局部性原理
局部性原理是指CPU訪問存儲器時,無論是存取指令還是存取數據,所訪問的存儲單元都趨於聚集在一個較小的連續區域中.
首先要明白局部性原理能解決的是什麼問題,也就是主存容量遠遠比緩存大,
CPU執行程序的時候需要使用內存塊,如果該內存塊在緩存上,那麼處理器直接從緩存上取該內存塊就行了,因爲緩存的數據傳輸的速率比內存快的多。
因爲主存容量大,所以要取的內存塊很可能不在緩存上,因此就要把這個內存塊移到緩存上。局部性原理就是解決這個問題:
時間局部性:程序有在一段時間內多次訪問同一個數據塊的傾向,這個寫程序的都知道;程序循環、堆棧等是產生時間局部性的原因
空間局部性:程序往往有訪問一個聚集空間的數據塊的傾向,參見數組的訪問;
InnoDB也遵守局部性原理:
在InnoDB中,數據會存儲到磁盤上,在真正處理數據時需要先將數據加載到內存,表中讀取某些記錄時,InnoDB存儲引擎不需要一條一條的把記錄從磁盤上讀出來,
InnoDB採取的方式是:將數據劃分爲若干個頁,以頁作爲磁盤和內存之間交互的基本單位,
InnoDB中頁的大小一般爲 16 KB,也就是說,當需要從磁盤中讀數據時每一次最少將從磁盤中讀取16KB的內容到內存中,每一次最少也會把內存中的16KB內容寫到磁盤中。
InnoDB數據頁結構
頁是InnoDB管理存儲空間的基本單位,一個頁的大小默認是16KB。
SHOW GLOBAL STATUS like 'Innodb_page_size';
頁結構:
名稱 | 中文名 | 佔用空間 | 簡單描述 |
File Header | 文件頭部 | 38字節 | 頁的一些通用信息 |
Page Header | 頁面頭部 | 56字節 | 數據頁專有的一些信息 |
Infimum + Supremum | 最小記錄和最大記錄 | 26字節 | 兩個虛擬的行記錄 |
User Records | 用戶記錄(存放數據的位置) | 不確定 | 實際存儲的行記錄內容 |
Free Space | 空閒空間 | 不確定 | 頁中尚未使用的空間 |
Page Directory | 頁面目錄 | 不確定 | 頁中的某些記錄的相對位置 |
File Trailer | 文件尾部 | 8字節 | 校驗頁是否完整 |
InnoDB行格式
一行記錄可以以不同的格式存在InnoDB中,行格式分別是Compact、Redundant、Dynamic和Compressed行格式。
默認是Dynamic格式
CREATE TABLE tb_emp1 ( id int(11) DEFAULT NULL, name varchar(25) , deptId int(11) DEFAULT NULL, salary float DEFAULT NULL )
我們可以在創建或修改表的語句中指定行格式:
CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名稱 ALTER TABLE 表名 ROW_FORMAT=行格式名稱
如下1.
CREATE TABLE tb_emp1 ( id int(11) DEFAULT NULL, name varchar(25) , deptId int(11) DEFAULT NULL, salary float DEFAULT NULL ) ROW_FORMAT=COMPACT
2.
ALTER TABLE tb_emp1 ROW_FORMAT=Compact
COMPACT行格式
記錄的額外信息
這部分信息是服務器爲了描述這條記錄而不得不額外添加的一些信息,這些額外信息分爲3類,分別是:
- 變長字段長度列表
- NULL值列表
- 記錄頭信息
1.變長字段長度列表
MySQL支持一些變長的數據類型,比如VARCHAR(M)、VARBINARY(M)、TEXT類型,BLOB類型,
這些數據類型修飾列稱爲變長字段,變長字段中存儲多少字節的數據不是固定的,所以我們在存儲真實數據的時候需要順便把這些數據佔用的字節數也存起來。
在Compact行格式中,把所有變長字段的真實數據佔用的字節長度都存放在記錄的開頭部位,從而形成一個變長字段長度列表。
CHAR是一種固定長度的類型,VARCHAR則是一種可變長度的類型。
VARCHAR(M),M代表最大能存多少個字符。( MySQL5.0.3以前是字節,以後就是字符)
2.NULL值列表
Compact行格式會把可以爲NULL的列統一管理起來,存一個標記爲在NULL值列表中,如果表中沒有允許存儲 NULL 的列,則 NULL值列表也不存在了。
- 二進制位的值爲1時,代表該列的值爲NULL。
- 二進制位的值爲0時,代表該列的值不爲NULL。
3.記錄頭信息
除了變長字段長度列表、NULL值列表之外,還有一個用於描述記錄的記錄頭信息,它是由固定的5個字節組成。5個字節也就是40個二進制位,不同的位代表不同的意思,如圖:
名稱 | 大小(單位:bit) | 描述 |
預留位1 | 1 | 沒有使用 |
預留位2 | 1 | 沒有使用 |
delete_mask | 1 | 標記該記錄是否被刪除 |
min_rec_mask | 1 | B+樹的每層非葉子節點中的最小記錄都會添加該標記 |
n_owned | 4 | 表示當前記錄擁有的記錄數 |
heap_no | 13 | 表示當前記錄在記錄堆的位置信息 |
record_type | 3 | 表示當前記錄的類型,0表示普通記錄,1表示B+樹非葉子節點記錄,2表示最小記錄,3表示最大記錄 |
next_record | 16 | 表示下一條記錄的相對位置 |
4.記錄的真實數據
記錄的真實數據除了我們自己定義的列的數據以外,還會有三個隱藏列:
列名 | 是否必須 | 佔用空間 | 描述 |
row_id | 否 | 6字節 | 行ID,唯一標識一條記錄 |
transaction_id | 是 | 6字節 | 事務ID |
roll_pointer | 是 | 7字節 | 回滾指針 |
實際上這幾個列的真正名稱其實是:DB_ROW_ID、DB_TRX_ID、DB_ROLL_PTR。
一個表沒有手動定義主鍵,則會選取一個Unique鍵作爲主鍵,如果連Unique鍵都沒有定義的話,則會爲表默認添加一個名爲row_id的隱藏列作爲主鍵。所以row_id是在沒有自定義主鍵以及Unique鍵的情況下才會存在的。
行溢出數據
VARCHAR(M)類型的列最多可以佔用65535個字節。其中的M代表該類型最多存儲的字符數量,如果我們使用ascii字符集的話,一個字符就代表一個字節,我們看看VARCHAR(65535)是否可用:
CREATE TABLE varchar_size_demo( c VARCHAR(65535) ) CHARSET=ascii ROW_FORMAT=Compact;
報錯信息表達的意思是:MySQL對一條記錄佔用的最大存儲空間是有限制的,除BLOB或者TEXT類型的列之外,其他所有的列(不包括隱藏列和記錄頭信息)佔用的字節長度加起來不能超過65535個字節。
這個65535個字節除了列本身的數據之外,還包括一些其他的數據,比如說我們爲了存儲一個VARCHAR(M)類型的列,其實需要佔用3部分存儲空間:
- 真實數據
- 變長字段真實數據的長度
- NULL值標識
如果該VARCHAR類型的列沒有NOT NULL屬性,那最多隻能存儲65532個字節的數據,因爲變長字段的長度佔用2個字節,NULL值標識需要佔用1個字節。
CREATE TABLE varchar_size_demo( c VARCHAR(65532) ) CHARSET=ascii ROW_FORMAT=Compact;
或者
CREATE TABLE varchar_size_demo( c VARCHAR(65533) not null ) CHARSET=ascii ROW_FORMAT=Compact; Query OK, 0 rows affected (0.02 sec)
記錄中的數據太多產生的溢出
一個頁的大小一般是16KB,也就是16384字節,而一個VARCHAR(M)類型的列就最多可以存儲65533個字節,這樣就可能出現一個頁存放不了一條記錄。
在Compact和Reduntant行格式中,對於佔用存儲空間非常大的列,在記錄的真實數據處只會存儲該列的一部分數據,把剩餘的數據分散存儲在幾個其他的頁中,
然後記錄的真實數據處用20個字節存儲指向這些頁的地址(當然這20個字節中還包括這些分散在其他頁面中的數據的佔用的字節數),從而可以找到剩餘數據所在的頁。
Dynamic和Compressed行格式
這兩種行格式類似於COMPACT行格式,只不過在處理行溢出數據時有點兒分歧,它們不會在記錄的真實數據處存儲一部分數據,而是把所有的數據都存儲到其他頁面中,只在記錄的真實數據處存儲其他頁面的地址。
另外,Compressed行格式會採用壓縮算法對頁面進行壓縮。
索引
索引創建過程
建表
CREATE TABLE `order` ( order_id int(11) );
使用explain 查看具體sql用到的索引
explain select * from `order` where order_id=1;
加索引
alter table `order` add index idIndex (order_id);
再次查詢
創建索引會對錶進行加鎖操作,所以在業務非高峯期操作;
表中索引並非越多越好,對錶操作(delete,insert,update)時也需要時時維護索引,隨着表中數據的增大,維護索引的成本也會增大;
聚簇索引和非聚簇索引(mysql的innodb,myisam)
A聚簇索引(主索引) :按物理位置存儲 ,排序快,一個表也就一個這樣的索引~ 索引的葉子節點保存的即爲對應的值
B聚簇索引(輔助索引): 先查到主鍵的目錄,再根據目錄查到具體的值所在位置 索引葉子節點保存的是數據所在的位置
B的查找過程,B->A->對應的值
非聚簇索引: 主索引結構與輔助索引的一致,都是存儲的指向鍵值的物理地址(只是主索引不允許空值及重複值)
聚簇索引的特點:
- 按主鍵值的大小進行記錄和頁的排序:
- 數據頁(葉子節點)裏的記錄是按照主鍵值從小到大排序的一個單向鏈表。
- 數據頁(葉子節點)之間也是是按照主鍵值從小到大排序的一個雙向鏈表。
- B+樹中同一個層的頁目錄也是按照主鍵值從小到大排序的一個雙向鏈表。
- B+樹的葉子節點存儲的是完整的用戶記錄,就是指這個記錄中存儲了所有列的值(包括隱藏列)。
具有這兩種特性的B+樹稱爲聚簇索引,所有完整的用戶記錄都存放在這個聚簇索引的葉子節點處。這種聚簇索引並不需要我們在MySQL語句中顯式的使用INDEX語句去創建。InnoDB存儲引擎會自動的爲我們創建聚簇索引。在InnoDB存儲引擎中,聚簇索引就是數據的存儲方式(所有的用戶記錄都存儲在了葉子節點),也就是所謂的索引即數據,數據即索引。
二級索引(複製索引)
聚簇索引只能在搜索條件是主鍵值時才能發揮作用,因爲B+樹中的數據都是按照主鍵進行排序的。當我們想以別的列作爲搜索條件時我們可以多建幾棵B+樹,不同的B+樹中的數據採用不同的排序規則。
二級索引與聚簇索引有幾處不同:
- 按指定的索引列的值來進行排序
- 葉子節點存儲的不是完整的用戶記錄,而只是索引列+主鍵。
- 目錄項記錄中不是主鍵+頁號,變成了索引列+頁號。
- 在對二級索引進行查找數據時,需要根據主鍵值去聚簇索引中再查找一遍完整的用戶記錄,這個過程叫做回表
聯合索引
以多個列的大小爲排序規則建立的B+樹稱爲聯合索引,本質上也是一個二級索引。
目錄項記錄的唯一性
我們需要保證在B+樹的同一層內節點的目錄項記錄除頁號這個字段以外是唯一的。所以對於二級索引的內節點的目錄項記錄的內容實際上是由三個部分構成的:
- 索引列的值
- 主鍵值
- 頁號
B+樹索引總結
- 每個索引都對應一棵B+樹。用戶記錄都存儲在B+樹的葉子節點,所有目錄記錄都存儲在非葉子節點。
- InnoDB存儲引擎會自動爲主鍵(如果沒有它會自動幫我們添加)建立聚簇索引,聚簇索引的葉子節點包含完整的用戶記錄。
- 可以爲指定的列建立二級索引,二級索引的葉子節點包含的用戶記錄由索引列 + 主鍵組成,所以如果想通過二級索引來查找完整的用戶記錄的話,需要通過回表操作,也就是在通過二級索引找到主鍵值之後再到聚簇索引中查找完整的用戶記錄。
- B+樹中每層節點都是按照索引列值從小到大的順序排序而組成了雙向鏈表,而且每個頁內的記錄(不論是用戶記錄還是目錄項記錄)都是按照索引列的值從小到大的順序而形成了一個單鏈表。如果是聯合索引的話,則頁面和記錄先按照聯合索引前邊的列排序,如果該列值相同,再按照聯合索引後邊的列排序。
- 通過索引查找記錄是從B+樹的根節點開始,一層一層向下搜索。由於每個頁面都按照索引列的值建立了頁目錄,所以在這些頁面中的查找非常快。