第2章 InnoDB存儲引擎
-一.InnoDB存儲引擎的版本
MySQL版本 | 版本 | 功能 |
MySQL5.1 | 老版本的InnoDB | 支持ACID(原子性、一致性、隔離性、持久性)、行鎖設計、MVCC(多版本併發控制技術) |
InnoDB 1.0.x | 繼承上述版本所有功能,增加了compress(壓縮)和dynamic(動態)頁格式 | |
MySQL5.5 | InnoDB 1.1.x | 繼承上述版本所有功能,增加了Linux AIO、多回滾段 |
MySQL5.6 | InnoDB 1.2.x | 繼承上述版本所有功能,增加了全文索引支持、在線索引添加 |
mysql在創建表的時候定義表的性質(也叫表的類型),共有三種:靜態表,動態表,壓縮表。默認是靜態表,如果存在varchar、blob、text字段,表類型就是動態了。
1.靜態表:
字段有固定長度,例如:char(20)。如果使用gbk字符集存儲中文username,將佔用40byte,如果username的實際內容沒有達到 40byte,將會填充空格,以達到40byte。速度很快,因爲mysql知道username總是從第41個字節開始,容易緩存,出現問題後也容易恢 復(mysql知道記錄的確切位置),需要更多的硬盤空間(如果有三個上面的字段,一條記錄就會佔120字節,即使實際只用了其中一部分)
2.動態表:
字段不定長(變長),這種表格式比較節省空間,但是複雜度更高,每條記錄都有一個header,作用就是表明該記錄有多長,所有的字符串列都是動態的(除非 小於4個字節,這種情況下,節省的空間可以忽略不計,增加的複雜度會反而會導致性能丟失),通常佔用比靜態表少的多地空間,但是必須經常維護(避免碎 片),例如:更新了用戶名somebody爲somebodyt,t不能立刻就出現在y的後面,因爲空間被其他記錄佔用,對於出現碎片的列,每個新連接會 損失6個字節。而且出現問題後不容易重建(前面我說的靜態表正好相反),如果碎片嚴重,有可能出現庫爆炸(^_^).
不包括連接的動態記錄的空間消耗可以用下面的公式計算:
3+(列數+7)/8+(字符列數)+數字列的打包尺寸+字符串長度+(空列的數量+7)/8
每條記錄的header可以表明那個字符串列是空的,那個數字列包含0(非空),在那種情況下不向磁盤存儲,非空字符串包含一個長度字節加上字符串內容。
壓縮表:
只讀,使用很少的空間,用myisampack工具創建,表要少得多,每條記錄分開壓縮,所以不能同時訪問,可以壓縮靜態表和動態表。
創建方法:myisampack [options] filename
-二.InnoDB存儲引擎的架構
2.1後臺線程
1.Master Thread
Master Thread是一個非常核心的後臺線程,主要負責將緩衝池中的數據異步刷新到磁盤,保證數據的一致性,包括髒頁的刷新、合併插入緩衝(INSERT BUFFER)、UNDO頁的回收等。
2.IO Thread
在InnoDB存儲引擎中大量使用了AIO(Async IO)來處理寫請求,這樣可以極大提高數據庫的性能。而IO Thread的工作主要是負責這些IO請求的回調處理。
可以通過命令show engine innodb status來觀察InnoDB中的IO Thread。
3.Purge Thread
事務提交後,其所使用的undolog可能不在需要,因此需要Purge Thread來回收已經使用並分配的undo頁。在InnoDB1.1版本之前,purge操作僅在Master Thread中完成。而從InnoDB1.1開始,purge操作可以獨立到單獨的線程中完成,以此來減少Master Thread的工作,從而提高CPU的使用率以及提升存儲引擎的性能。
從InnoDB1.2開始,InnoDB支持多個Purge Thread,這樣做的目的是爲了進一步加快undo頁的回收。
Undolog:在操作任何數據之前,首先將數據備份到一個地方(undolog),然後再修改;這樣就算更改到一半的時候,出現意外更改沒有完成,則還可以恢復原來數據。
Redolog:每當操作數據前,將數據真正更改的時候,先將相關操作寫入重做日誌。這樣當斷電或者發生意外,導致後續操作未能完成,系統恢復後,還可以繼續操作。
4.Page Cleaner Thread
Page Cleaner Thread是在InnoDB1.2.x版本中引入的。其作用是將之前的版本中髒頁的刷新操作都放入單獨的線程中完成。其目的是減輕原Master Thread的工作及對於用戶查詢線程的阻塞,進一步提高InnoDB存儲引擎的性能。
2.2內存
1.緩衝池
InnoDB存儲引擎是基於磁盤存儲的,並將其中的記錄按照頁的方式的方式進行管理。
緩衝池簡單來說就是一塊內存區域,通過內存的速度來彌補磁盤速度較慢對數據庫性能的影響。常見的在數據庫中操作頁有兩種情況:首先是讀取頁操作,會先將從磁盤讀到的頁放在緩衝池中,下一次再讀相同的頁時,會首先判斷該頁是否再緩衝池中;然後還有修改操作,當修改了緩衝池中的頁,會以一定頻率刷新到磁盤上。注:刷新回磁盤不是再每次頁發生更改的時候觸發,而是通過一種稱爲Checkpoint的機制刷新回磁盤。
緩衝池的大小直接影響着數據庫的整體性能。
下圖緩衝池中還有undo頁。
從InnoDB 1.0.x版本開始,允許有多個緩衝池實例。每個頁根據哈希值平均分配到不同緩衝池實例中。可以減少數據庫內部資源的競爭,增加數據庫的併發處理能力。
2.管理緩衝池內存區域
LRU List
數據庫中的緩衝池是通過LRU(最近最少使用)算法來進行管理的。即最頻繁使用的頁在LRU列表的前端,而最少使用的頁在lRU列表的尾端。當緩衝池不能存放新讀取到的頁時,將首先釋放LRU列表中尾端的頁。
InnoDB存儲引擎對傳統的LRU算法做了一些優化,LRU列表中還加入了midpoint位置。即新讀入的頁不是先放在LRU列表的首部,而是放在midpoint位置。原因是,直接放入列表首端的話會,那麼某些sql操作可能會使緩衝池中的頁被刷新出,從而影響緩衝池的效率。
Free List
LRU列表用來管理已經讀取的頁,但當數據庫剛啓動時,LRU列表時空的,即沒有任何的頁。這時頁都存放在free列表中,當需要從緩衝池中分頁時,首先從free列表中查找是否有可用的空閒頁,如有則將該頁從free列表中刪除,放入到LRU列表中。否則,根據LRU算法,淘汰LRU列表末尾的頁,將該內存空間分配給新的頁。
Innodb存儲引擎從1.0.x版本開始只是壓縮頁的功能。原來LRU列表中的頁 默認爲16k,而對於非16k的頁,是通過unzip_LRU列表進行管理的。
Flush List
在LRU列表中的頁被修改後,稱該頁爲髒頁,即緩衝池中的頁和磁盤上的頁的數據產生了不一致。這時數據庫會通過checkpoint機制將髒頁刷新回磁盤,而flush列表中的頁即爲髒頁列表,需要注意的是,髒頁既存在與LRU列表中,也存在與flush列表中。LRU列表用來管理緩衝池中的頁的可用性,flush列表用來管理將頁刷新回磁盤,二者互不影響。
3.重做日誌緩衝
InnoDB存儲引擎會首先將重做日誌信息先放入到這個緩衝區,然後按一定頻率將其刷新到重做日誌文件。重做日誌緩衝一般不需要設置很大。
通常情況下,8MB的重做日誌緩衝池足以滿足絕大部分的應用,因爲重做日誌在以下三種情況會將重做日誌緩衝中的內容刷新到外部磁盤的重做日誌文件中。
1. master Thread每一秒將重做日誌緩衝刷新到重做日誌文件;
2. 每個事物提交時會將重做日誌緩衝刷新到重做日誌文件;
3. 當重做日誌緩衝池剩餘空間小於1/2時,重做日誌緩衝刷新到重做日誌文件。
4.額外的內存池
在innodb存儲引擎中,對內存的管理是通過一種稱爲內存堆的方式進行的。在對一些數據結構本身的內存進行分配時,需要從額外的內存池中進行申請,當該區域的內存不夠時,會從緩衝池中進行申請。
-三.Checkpoint技術
爲了協調CPU和磁盤速度的鴻溝,因此頁的操作首先都是在緩衝池中完成的。當一個頁在緩衝池中被DML語句修改,就會成爲髒頁,這時就需要將其刷新回磁盤。但是如果每次修改都將其刷新會嚴重影響性能。
同時,如果在從緩衝池將頁的數據刷新到磁盤時發生宕機,那麼數據就不能恢復了。爲了避免發生數據丟失的問題,當事務提交的時候,先寫重做日誌,再修改頁。當由於發生宕機導致數據丟失的時候,通過重做日誌來完成數據的恢復。
但是重做日誌不能無限增大
因此checkpoint技術的目的是解決以下幾個問題:
01.縮短數據庫恢復的時間
當數據庫發生宕機 不需要重做所有日誌,只需要重做checkpoint之後的操作。
02.當緩衝池不可用時,將數據刷新回磁盤
當緩衝池溢出頁,那麼也會強制執行checkpoint操作。
03.重做日誌不可用時,刷新髒頁
重做日誌可以被重用的部分是指這些重做日誌已經不再需要,當數據庫發生宕機時,數據庫恢復操作不需要這部分的重做日誌,因此這部分可以被覆蓋重用。若此時重做日誌還需要使用,那麼必須強制產生checkpoint,將緩衝池中的頁至少刷新到當前重做日誌的位置。
1.Sharp Checkpoint
發生在數據庫關閉時將所有的髒頁都刷新回磁盤。
2.Fuzzy Checkpoint
若在數據庫運行時也使用sharp checkpoint,那麼數據庫的可用性就會受到很大的影響。所以用fuzzy checkpoint只刷新部分髒頁。
1. Master Thread Checkpoint
Master Thread中會發生Checkpoint,差不多以每秒或者每十秒的速度從緩衝池的髒頁列表中刷新一定比例的頁回磁盤。這個過程是異步的,不影響MySQL的其他操作,用戶查詢線程不會影響。
2. Flush_lru_list Checkpoint
Flush_lru_list Checkpoint是因爲innodb存儲引擎需要保證lru列表中需要有差不多100個空閒頁可供使用。
3. Async/Sync Flush Checkpoint
這個指重做日誌文件不可用的情況,這時強制將一些頁刷新回磁盤,而此時是從髒頁列表中選取的。
4. Dirty Page too much Checkpoint
當髒頁數量太多,導致innodb存儲引擎強制進行checkpoint。其目的總的來說還是爲了保證緩衝池中有足夠可用的頁。
-四、Master Thread工作方式
1.innodb1.0.x版本之前的master thread
Master thread具有最高的線程優先級別。其內部由多個循環組成:主循環(loop)、後臺循環(backgroud loop)、刷新循環(flush loop)、暫停循環(suspend loop)。
主循環:
其中有兩大部分操作——每秒的操作和每10秒操作。
每秒的操作:
l 日誌緩衝刷新到磁盤,即使這個事務還沒有提交(總是);
即使某個事務還沒有提交,InnoDB 存儲引擎仍然每秒會將重做日誌緩衝中的內容刷新到重做日誌文件。這一點是必須要知道的,因爲這可以很好地解釋爲什麼再大的事務提交(commit)的時間也是很短的。
l 合併插入緩衝(可能);
合併插入緩衝(Insert Buffer)並不是每秒都會發生的。InnoDB 存儲引擎會判斷當前一秒內發生的IO 次數是否小於5 次,如果小於5 次,InnoDB 認爲當前的IO 壓力很小,可以執行合併插入緩衝的操作。
l 至少刷新100個innodb的緩衝池中的髒頁到磁盤(可能);
同樣,刷新100 個髒頁也不是每秒都會發生的。InnoDB 存儲引擎通過判斷當前緩衝池中髒頁的比例(buf_get_modified_ratio_pct)是否超過了配置文件中innodb_max_dirty_pages_pct 這個參數(默認爲90,代表90%),如果超過了這個閾值,InnoDB 存儲引擎認爲需要做磁盤同步的操作,將100 個髒頁寫入磁盤中。
l 如果當前沒有用戶活動,則切換到background loop(可能)。
每10秒的操作:
l 刷新100個髒頁到磁盤(可能的情況下);
l 合併至多5個插入緩衝(總是);
l 將日誌緩衝刷新到磁盤(總是);
l 刪除無用的undo頁(總是);
l 刷新100個或者10個髒頁到磁盤(總是)。
在以上的過程中,InnoDB 存儲引擎會先判斷過去10 秒之內磁盤的IO 操作是否小於200 次,如果是,InnoDB 存儲引擎認爲當前有足夠的磁盤IO 操作能力,因此將100個髒頁刷新到磁盤。接着,InnoDB 存儲引擎會合並插入緩衝。不同於每秒一次操作時可能發生的合併插入緩衝操作,這次的合併插入緩衝操作總會在這個階段進行。之後,InnoDB 存儲引擎會再進行一次將日誌緩衝刷新到磁盤的操作。這和每秒一次時發生的操作是一樣的。
接着InnoDB 存儲引擎會進行一步執行full purge 操作, 即刪除無用的Undo頁。對錶進行update、delete 這類操作時,原先的行被標記爲刪除,但是因爲一致性讀(consistent read)的關係,需要保留這些行版本的信息。但是在full purge 過程中,InnoDB 存儲引擎會判斷當前事務系統中已被刪除的行是否可以刪除,比如有時候可能還有查詢操作需要讀取之前版本的undo 信息,如果可以刪除,InnoDB 會立即將其刪除。
從源代碼中可以發現,InnoDB 存儲引擎在執行full purge 操作時,每次最多嘗試回收20個undo 頁。
然後,InnoDB 存儲引擎會判斷緩衝池中髒頁的比例(buf_get_modified_ratio_pct),如果有超過70% 的髒頁,則刷新100 個髒頁到磁盤,如果髒頁的比例小於70%,則只需刷新10% 的髒頁到磁盤。
後臺循環:
若當前沒有用戶活動(數據庫空閒時)或者數據庫關閉(shutdown),就會切換到這個循環。
background loop 會執行以下操作:
u 刪除無用的Undo 頁(總是);
u 合併20 個插入緩衝(總是);
u 跳回到主循環(總是);
u 不斷刷新100 個頁直到符合條件(可能,跳轉到flush loop 中完成)。
若flush loop 中也沒有什麼事情可以做了,InnoDB 存儲引擎會切換到suspend__loop,將Master Thread 掛起,等待事件的發生。若用戶啓用(enable)了InnoDB 存儲引擎,卻沒有使用任何InnoDB 存儲引擎的表,那麼Master Thread 總是處於掛起的狀態。
2.innodb1.2.x版本之前的master thread
問題總結:由於innodb硬編碼(hard coding),會限制其刷新髒頁和合並插入緩衝的數量,即使可以操作更多。
l 從innodb1.0.x引入了innodb_io_capacity,用來表示磁盤IO的吞吐量,默認值爲200。
l 調整參數innodb_max_dirty_pages_pct(髒頁佔緩衝池的比例)爲75
l 從innodb1.0.x開始引入另外一個參數innodb_adaptive_flushing(自適應地刷新),影響每秒刷新髒頁的數量。之前是根據一個參數的值來進行,現在根據函數來執行。
l 將purge thread放在單獨的線程中。
3.innodb1.2.x版本的master thread
對於刷新髒頁的操作,從master thread線程分離到一個單獨的page cleaner thread中。
-五、InnoDB關鍵特性
插入緩衝(insert buffer)
1. Insert Buffer
Insert Buffer 和數據頁一樣,也是物理頁的一個組成部分。
在InnoDB 存儲引擎中,主鍵是行唯一的標識符。通常應用程序中行記錄的插入順序是按照主鍵遞增的順序進行插入的。因此,插入聚集索引(Primary Key)一般是順序的,不需要磁盤的隨機讀取。比如按下列SQL 定義表:
CREATE TABLE t (
a INT AUTO_INCREMENT,
b VARCHAR(30),
PRIMARY KEY(a)
);
注意 並不是所有的主鍵插入都是順序的。若主鍵類是UUID 這樣的類,那麼插入和輔助索引一樣,同樣是隨機的。即使主鍵是自增類型,但是插入的是指定的值,而不是NULL 值,那麼同樣可能導致插入並非連續的情況。
但是不可能每張表上只有一個聚集索引,更多情況下,一張表上有多個非聚集的輔助索引(secondary index)。比如,用戶需要按照b 這個字段進行查找,並且b 這個字段不是唯一的,即表是按如下的SQL 語句定義的:
CREATE TABLE t (
a INT AUTO_INCREMENT,
b VARCHAR(30),
PRIMARY KEY(a),
key(b)
);
在這樣的情況下產生了一個非聚集的且不是唯一的索引。在進行插入操作時,數據頁的存放還是按主鍵a 進行順序存放的,但是對於非聚集索引葉子節點的插入不再是順序的了,這時就需要離散地訪問非聚集索引頁,由於隨機讀取的存在而導致了插入操作性能下降。當然這並不是這個b 字段上索引的錯誤,而是因爲B+ 樹的特性決定了非聚集索引插入的離散性。
InnoDB 存儲引擎開創性地設計了Insert Buffer,對於非聚集索引的插入或更新操作,不是每一次直接插入到索引頁中,而是先判斷插入的非聚集索引頁是否在緩衝池中,若在,則直接插入;若不在,則先放入到一個Insert Buffer 對象中,好似欺騙數據庫這個非聚集的索引已經插到葉子節點,而實際並沒有,只是存放在另一個位置。然後再以一定的頻率和情況進行Insert Buffer 和輔助索引頁子節點的merge(合併)操作,這時通常能將多個插入合併到一個操作中(因爲在一個索引頁中),這就大大提高了對於非聚集索引插入的性能。
(如果每次直接插入,需要離散的查找要插入的索引頁,性能低下)
然而Insert Buffer 的使用需要同時滿足以下兩個條件:
l 索引是輔助索引(secondary index);
l 索引不是唯一(unique)的。
2. Change Buffer
InnoDB 從1.0.x 版本開始引入了Change Buffer,可將其視爲Insert Buffer 的升級。
從這個版本開始,InnoDB 存儲引擎可以對DML 操作——INSERT、DELETE、UPDATE都進行緩衝,他們分別是:Insert Buffer、Delete Buffer、Purge buffer。
當然和之前Insert Buffer 一樣,Change Buffer 適用的對象依然是非唯一的輔助索引。
對一條記錄進行UPDATE 操作可能分爲兩個過程:
l 將記錄標記爲已刪除;
l 真正將記錄刪除。
3. Merge Insert Buffer
Insert Buffer 中的記錄何時合併(merge)到真正的輔助索引中呢?
概括地說,Merge Insert Buffer 的操作可能發生在以下幾種情況下:
l 輔助索引頁被讀取到緩衝池時;
l Insert Buffer Bitmap 頁追蹤到該輔助索引頁已無可用空間時;
l Master Thread。
第一種情況爲當輔助索引頁被讀取到緩衝池中時,例如這在執行正常的SELECT 查詢操作,這時需要檢查Insert Buffer Bitmap 頁,然後確認該輔助索引頁是否有記錄存放於Insert Buffer B+ 樹中。若有,則將Insert Buffer B+ 樹中該頁的記錄插入到該輔助索引頁中。可以看到對該頁多次的記錄操作通過一次操作合併到了原有的輔助索引頁中,因此性能會有大幅提高。
Insert Buffer Bitmap 頁用來追蹤每個輔助索引頁的可用空間,並至少有1/32 頁的空間。若插入輔助索引記錄時檢測到插入記錄後可用空間會小於1/32 頁,則會強制進行一個合併操作,即強制讀取輔助索引頁,將Insert Buffer B+ 樹中該頁的記錄及待插入的記錄插入到輔助索引頁中。這就是上述所說的第二種情況。
還有一種情況,之前在分析Master Thread 時曾講到,在Master Thread 線程中每秒或每10 秒會進行一次Merge Insert Buffer 的操作,不同之處在於每次進行merge 操作的頁的數量不同。
兩次寫(double write)
如果說Insert Buffer 帶給InnoDB 存儲引擎的是性能上的提升,那麼doublewrite(兩次寫)帶給InnoDB 存儲引擎的是數據頁的可靠性。
當發生數據庫宕機時,可能InnoDB 存儲引擎正在寫入某個頁到表中,而這個頁只寫了一部分,比如16KB 的頁,只寫了前4KB,之後就發生了宕機,這種情況被稱爲部分寫失效(partial page write)。
有經驗的DBA 也許會想,如果發生寫失效,可以通過重做日誌進行恢復。這是一個辦法。但是必須清楚地認識到,重做日誌中記錄的是對頁的物理操作,如偏移量800,寫'aaaa' 記錄。如果這個頁本身已經發生了損壞,再對其進行重做是沒有意義的。
Doublewrite過程:
doublewrite 由兩部分組成,一部分是內存中的doublewrite buffer,大小爲2MB,另一部分是物理磁盤上共享表空間中連續的128 個頁,即2 個區(extent),大小同樣爲2MB。在對緩衝池的髒頁進行刷新時,並不直接寫磁盤,而是會通過memcpy 函數將髒頁先複製到內存中的doublewrite buffer,之後通doublewrite buffer 再分兩次,每次1MB 順序地寫入共享表空間的物理磁盤上,然後馬上調用fsync 函數,同步磁盤,避免緩衝寫帶來的問題。在這個過程中,因爲doublewrite 頁是連續的,因此這個過程是順序寫的,開銷並不是很大。在完成doublewrite 頁的寫入後,再將doublewrite buffer 中的頁寫入各個表空間文件中,此時的寫入則是離散的。
如果操作系統在將頁寫入磁盤的過程中發生了崩潰,在恢復過程中,InnoDB 存儲引擎可以從共享表空間中的doublewrite 中找到該頁的一個副本,將其複製到表空間文件,再應用重做日誌。
自適應哈希索引(adaptive hash index)
InnoDB 存儲引擎會監控對錶上各索引頁的查詢。如果觀察到建立哈希索引可以帶來速度提升,則建立哈希索引,稱之爲自適應哈希索引(Adaptive Hash Index,AHI)
異步IO(async IO)
爲了提高磁盤操作性能,當前的數據庫系統都採用異步IO(Asynchronous IO,AIO)的方式來處理磁盤操作。InnoDB 存儲引擎亦是如此。
與AIO 對應的是Sync IO,即每進行一次IO 操作,需要等待此次操作結束才能繼續接下來的操作。但是如果用戶發出的是一條索引掃描的查詢,那麼這條SQL 查詢語句可能需要掃描多個索引頁,也就是需要進行多次的IO 操作。在每掃描一個頁並等待其完成後再進行下一次的掃描,這是沒有必要的。用戶可以在發出一個IO 請求後立即再發出另一個IO 請求,當全部IO 請求發送完畢後,等待所有IO 操作的完成,這就是AIO。
在InnoDB1.1.x 之前,AIO 的實現通過InnoDB 存儲引擎中的代碼來模擬實現。而從InnoDB 1.1.x 開始(InnoDB Plugin 不支持),提供了內核級別AIO 的支持,稱爲NativeAIO。因此在編譯或者運行該版本MySQL 時,需要libaio 庫的支持。
刷新臨界頁(flush neighbor page)
InnoDB 存儲引擎還提供了Flush Neighbor Page(刷新鄰接頁)的特性。其工作原理爲:當刷新一個髒頁時,InnoDB 存儲引擎會檢測該頁所在區(extent)的所有頁,如果是髒頁,那麼一起進行刷新。這樣做的好處顯而易見,通過AIO 可以將多個IO 寫入操作合併爲一個IO 操作,故該工作機制在傳統機械磁盤下有着顯著的優勢。但是需要考慮到下面兩個問題:
l 是不是可能將不怎麼髒的頁進行了寫入,而該頁之後又會很快變成髒頁?
l 固態硬盤有着較高的IOPS,是否還需要這個特性?
爲此,InnoDB 存儲引擎從1.2.x 版本開始提供了參數innodb_flush_neighbors,用來控制是否啓用該特性。對於傳統機械硬盤建議啓用該特性,而對於固態硬盤有着超高IOPS 性能的磁盤,則建議將該參數設置爲0,即關閉此特性。
第四章表
查看頁的情況#py_innodb_page_info.py -v t.ibd
4.1索引組織表
定義:
表都是根據主鍵順序組織存放的,這種存儲方式的表稱爲索引組織表。在innodb存儲引擎中,每張表都有個主鍵,如果在創建表時沒有顯示的定義主鍵,則innodb存儲引擎會按如下方式選擇或創建主鍵:
u 首先判斷表中是否有非空的唯一索引,如果有,則該列即爲主鍵。
u 如果不符合上述條件,innodb存儲引擎會自動創建一個6字節大小的指針。
注:當表中有多個非空唯一索引時,innodb存儲引擎將選擇建表時第一個定義的非空唯一索引爲主鍵。主鍵的選擇根據的是定義索引的順序,而不是建表時列的選擇。
操作:
#select a,b,c,d,_rowid from z;
_rowid可以顯示錶的主鍵(只能顯示單個列的主鍵的情況,對於多列組成的主鍵就不行了)
4.2innodb邏輯存儲結構
從innodb存儲引擎的邏輯存儲結構來看,所有的數據都被邏輯地存放在一個空間中,成爲表空間。表空間又由段,區,頁組成。
4.2.1表空間
在默認情況下innodb存儲引擎有一個共享表空間ibdata1,即所有的數據都存放在這個表空間中。如果用戶啓用了參數innodb_file_per_table,則每張表內的數據可以單獨放到一個表空間中。
需要注意的是每張表的表空間內存放的只是數據,索引和插入緩衝birnap頁,其他類的數據,如回滾信息,插入緩衝索引頁、系統事務信息,二次寫緩衝等還是存放在原來的共享表空間內。即使啓用了參數innodb_file_per_table之後,共享表空間還是會不斷增加其大小。
4.2.2段
表空間由各個段組成,常見的段有數據段、索引段、回滾段等。
對段的管理一般有引擎自身來完成,dba不能也沒有必要對其進行控制。
4.2.3區
區由連續頁組成,在任何情況下每個區的大小爲1MB。
默認情況下,innodb存儲引擎頁的大小爲16KB,即一個區中一共有64個連續的頁。Innodb1.0.x開始引入壓縮頁;innodb1.2.x增加了參數innodb_page_size,可以設置默認頁大小設置爲4K,8K。
這裏有個問題:在用戶啓用了參數innodb_file_per_table後,創建表默認爲96KB。區中是64個連續的頁,創建的表的大小至少是1MB纔對啊?其實這是因爲在每個段開始時,先用32個頁大小的碎片頁(frament page)來存放數據,在使用完這些頁之後纔是64個連續頁的申請。
4.2.4頁
Innodb有頁的概念,頁是innodb磁盤管理的最小單位。
默認頁大小爲16K,從innodb1.2.x開始,可以通過參數innodb_page_size將頁的大小設置爲4K,8K,16K.
常見的頁類型有:
數據頁、(B-tree Node)
undo頁、(undo Log Page)
系統頁、(System Page)
事務數據頁、(transaction system Page)
插入緩衝位圖頁、(Insert buffer bitmap)
插入緩衝空閒列表頁、(Insert buffer free list)
未壓縮的二進制大對象頁、(uncompressed blob page)
壓縮的二進制打對象頁。(compressed blob page)
4.2.5行
Innodb存儲引擎是面向列的(row-oriented),也就是說數據是按行進行存放的。每個頁存放的行記錄也是有硬性定義的,最多允許存放16K/2-200行的記錄,即7992行記錄。
4.3innodb行記錄格式
如果用戶自己知道頁中行記錄的組織規則,也可以自行通過編寫工具的方式來讀取其中的記錄,比如py_innodb_page_info。
Innodb1.0.x之前 | Compact | Antelope文件格式 |
Redundant | ||
Innodb1.0.x開始引入 | Compressed | Barracuda文件格式 |
Dynamic |
01.compact行記錄格式
變長字段長度列表 (列長度小於255字節,用1字節表示;大於255字節,用2字節表示) | Null標誌位 (佔1字節) | 記錄頭信息 (佔5字節) | 列1數據 | 列2數據 | ...... |
特別注意:null不佔用該部分任何空間,即null除了佔有null標誌位,實際存儲不佔有任何空間;另外每行數據除了用戶定義的列外,還有兩個隱藏列:事務ID列(6字節)和回滾指針列(7字節);若innodb沒有定義主鍵,每行還會增加一個6字節大小的rowid列。
02.行溢出數據
Varchar類型
65535——>65535-2(2爲當列長度小大於255字節,用2字節表示)=65533——>65535-2-1(1爲空標誌位)=65532
注1:如果允許建立varchar(65535),會拋出一個warning,即會將其變成text類型。
注2:varchar(N),N指的是字符長度,而文檔說明varchar類型最大支持65535,單位是字節。
注3:MySQL官方手冊中定義的65535長度是指所有varchar類型的長度總和,如果列的長度總和超出這個長度,依然無法創建。
大類型溢出
Innodb存儲引擎的頁爲16K,即16384字節,存放不下65535.
在一般情況下,innodb存儲引擎的數據都是存放在頁類型爲B-tree node中。但當發生行溢出時,數據存放在頁類型爲Uncompress Blob頁中。
對於varchar保存數據到數據頁時,從8098字節開始往blob頁中存放。
注:
對於text和blob的數據類型,用戶總是以爲它們是存放在uncompressed blob page中的,其實這也不準確。但同時,既然用戶使用了blob列類型,一般不可能存放長度這麼小的數據。因此在大多數的情況下blob的行數據還是會發生行溢出,實際數據保存在blob頁中,數據頁只保存數據的前768字節。
03.compressed和dynamic行記錄格式
這兩種格式對於存放在blob中的數據採用了完全的行溢出的方式,即在數據頁中只存放20個字節,實際的數據都存放在off page中。
Compressed行記錄格式的另一個功能就是,存儲在其中的行數據會以zlib的算法進行壓縮,因此對於blob、text、varchar這類大長度類型的數據能夠進行非常有效的存儲。
04.char的行結構存儲
Char類型被明確視爲變長字符類型,對於未能佔滿長度的字符還是填充0x20。因此可以認爲在多字節字符集的情況下,char和varchar的實際存儲基本是沒有區別的。
4.4innodb數據頁結構
01.file header
用來記錄頁的一些頭信息。
02.page header
用來記錄數據頁的狀態信息。
03.infimum和supremum record
這是數據頁中的兩個虛擬行記錄,用來限定記錄的邊界。
04.user record和free space
User record:實際存儲行記錄的內容。再次強調,innodb存儲引擎表總是b+樹索引組織的。
Free space:就是指空閒空間,同樣也是個鏈表數據結構。在一條記錄被刪除後,該空間會被加入到空閒鏈表中。
05.page directory
存放了記錄的相對位置(注意:這裏存放的是頁相對位置,而不是偏移量)
06.file trailer
爲了檢測頁是否已經完整寫入磁盤(如可能發生的寫入過程中磁盤損壞、機器關機等),innodb存儲引擎的頁中設置了file trailer。
操作:
當建立表並將其中插入數據,可以使用工具來分析。(例如分析t.ibd)
#py_innodb_page_info.py -v t.ibd
...
page offset 00000003,page type <B-tree Node>,page level <0000>
...
(頁類型爲B-tree node的頁存放的即是表中行的實際數據了)
第五章索引與算法
5.1 Innodb存儲引擎索引概述
Innodb存儲引擎支持一下幾種常見的索引:
l B+樹索引
l 全文索引
l 哈希索引
前面提過,innodb存儲引擎支持的哈希索引是自適應的,innodb存儲引擎會根據表的使用情況自動爲表生成哈希索引,不能人爲干預是否在一張表中生成哈希索引。
另一個容易被忽略的問題是:B+樹索引並不能找到一個給定鍵值的具體行。B+樹索引能找到的只是被查找數據行所在的頁。然後數據庫通過把頁讀入到內存,再在內存中進行查找,最好得到要查找的數據。
5.2 B+樹索引
http://blog.51cto.com/qinbin/1877928
B+樹索引的本質就是B+樹在數據庫中的實現。但是B+索引在數據庫中有一個特點是高扇出性,因此在數據庫中,B+樹的高度一般在2~4層,這也就是說查找某一鍵值的行記錄時最多只需要2到4次IO。
扇入:指直接調用該模塊的上級模塊的個數。
扇出:是指該模塊直接調用的下級模塊的個數。
數據庫中的B+樹索引可以分爲聚集索引(clustered index)和輔助索引(secondary index),但是不管是聚集還是輔助索引,其內部都是B+樹的,即高度平衡的,葉子節點存放着所有的數據。聚集索引和輔助索引不同的是,葉子節點存放的是否是一整行的信息。
聚集索引
聚集索引就是按照每張表的主鍵構造一顆B+樹,同時葉子節點中存放的是整張表的行記錄數據,葉子節點也成稱爲數據頁。聚集索引的這個特性決定了索引組織表中數據也是索引的一部分。同B+樹數據結構一樣,每個數據頁通過一個雙向鏈表來進行鏈接。
由於實際的數據頁只能按照一顆B+樹進行排序,因此每張表只能擁有一個聚集索引。在多數情況下,查詢優化器傾向於採用聚集索引。因爲聚集索引能夠在B+樹索引的葉子節點上直接找到數據。此外,由於定義了數據的邏輯順序,聚集索引能夠特別快的訪問針對範圍值的查詢。查詢優化器能夠快速發現某一段範圍的數據頁需要掃描。
注意:聚集索引的存儲並不是物理上連續的,而是邏輯上連續的。這其中的兩點:一是前面說過的頁通過雙向鏈表鏈接,頁是按照主鍵的順序排序;另一點是每個頁中的記錄也是通過雙向鏈表進行維護的,物理存儲上可以同樣不按照主鍵存儲。
聚集索引的另外一個好處是,它對於主鍵的排序查找和範圍查找速度非常快。
使用order by對記錄進行排序,在實際過程中並沒有進行所謂的filesort操作,而這就是因爲聚集索引的特點。
輔助索引
對於輔助索引(非聚集索引),葉子節點並不包含行記錄的全部數據。葉子節點除了包含鍵值外,每個葉子節點中的索引行中還包含了一個書籤(bookmark)。該書籤用來告訴innodb存儲引擎哪裏可以找到與索引相對於的行數據。由於innodb存儲引擎表示索引組織表,因此innodb存儲引擎的輔助索引的書籤就是相應行數據的聚集索引鍵。
輔助索引的存在並不影響數據在聚集索引中的組織,因此每張表上可以有多個輔助索引。當通過輔助索引來尋找數據時,innodb存儲引擎會遍歷輔助索引並通過頁基本的指針獲得指向主鍵索引的主鍵,然後再通過主鍵索引來找到一個完整的行記錄。舉例來說,如果在一顆高度爲3的輔助索引樹中查找數據,那需要對這顆輔助索引樹遍歷3次找到指定主鍵,如果聚集索引樹的高度同樣爲3,那麼還需要對聚集索引樹進行3次查找,最終找到一個完整的行數據所在的頁,因此一個需要6次邏輯io訪問以得到最終的一個數據頁。
5.3B+樹索引的使用
聯合索引
索引覆蓋
Innodb存儲引擎支持索引覆蓋,即從輔助索引中就可以得到查詢的記錄,而不需要查詢聚集索引中的記錄。使用索引覆蓋的一個好處是輔助索引不包含整行記錄的所有信息,故其大小要遠小於聚集索引,因此可以減少大量的IO操作。
覆蓋索引的另一個好處是對某些統計問題而言。例如進行查詢:select count(*) from buy_log;innodb儲存引擎並不會選擇查詢聚集索引來進行統計。由於buy_log表上還有輔助索引,而輔助索引遠小於聚集索引,選擇輔助索引可以減少io操作。