前言
不必了解 MySQL?
和Percona Server for MySQL
如何構建索引。 但是,如果您了解處理過程,那么當您想為數據插入保留適當數量的空間時,它可能會有所幫助。 從 MySQL 5.7 開始,開發人員改變了他們為 InnoDB 構建二級索引的方式,應用了自底向上而不是早期版本中使用的自頂向下方法。 在這篇文章中,我將通過一個示例來說明如何構建 InnoDB
索引。 最后,我將解釋如何使用這種理解來為 innodb_fill_factor
設置一個合適的值。
索引構建過程
要在現有數據的表上建立索引,在 InnoDB 中有以下階段:
- 讀取階段(從聚集索引讀取并構建二級索引條目)
- 合并排序階段
- 插入階段(將排序后的記錄插入二級索引)
在 5.6 版本之前,MySQL 通過一次插入一條記錄來構建二級索引。這是一種“自上而下”的方法。對插入位置的搜索從根(頂部)開始,并到達相應的葉頁(向下)。記錄插入到光標指向的葉頁上。在查找插入位置以及進行頁面拆分和合并(在根級別和非根級別)方面,成本很高。你怎么知道發生了太多的頁面拆分和合并?您可以在我的同事 Marco Tusa 較早的博客中了解這一點,請點擊此處。
從 MySQL 5.7 開始,添加索引期間的插入階段使用“排序索引構建”,也稱為“批量加載索引”。在這種方法中,索引是“自下而上”構建的。即葉頁面(底部)首先構建,然后非葉級別直到根。
用例
在這些情況下使用排序索引構建:
* ALTER TABLE t1 ADD INDEX (or CREATE INDEX)
* ALTER TABLE t1 ADD FULLTEXT INDEX
* ALTER TABLE t1 ADD COLUMN, ALGORITHM=INPLACE
* OPTIMIZE TABLE t1
對于最后兩個用例,ALTER
創建一個中間表。中間表索引(主鍵索引和二級索引)是使用“排序索引構建”構建的。
算法
- 在 Level 0創建一個頁面。同時創建一個指向該頁面的游標。
- 使用 Level 0 的游標插入數據到頁面,直到填滿。
- 一旦頁面已滿,創建一個兄弟頁面(不插入數據到兄弟頁面)。
- 為當前滿頁創建一個節點指針(子頁中的最小鍵),并在上一層(父頁)插入一個節點指針。
- 在上層,檢查游標是否已經定位。如果沒有,請為該級別創建一個父頁面和一個游標。
- 在父頁面插入節點指針。
- 如果父頁面已滿,請重復步驟 3、4、5、6。
- 現在插入兄弟頁面并使游標指向兄弟頁面。
- 在所有插入的末尾,每個級別都有一個指向最右側頁面的游標。提交所有游標(意味著提交修改頁面的小事務,釋放所有鎖存器)。
為簡單起見,上述算法跳過了有關壓縮頁面和處理 BLOB(外部存儲的 BLOB)的細節。
自下而上構建索引的演示
使用一個示例,讓我們看看如何構建二級索引,自下而上。再次為簡單起見,假設葉頁和非葉頁中允許的最大記錄數為 3。
CREATE TABLE t1 (a INT PRIMARY KEY, b INT, c BLOB);
INSERT INTO t1 VALUES (1, 11, 'hello111');
INSERT INTO t1 VALUES (2, 22, 'hello222');
INSERT INTO t1 VALUES (3, 33, 'hello333');
INSERT INTO t1 VALUES (4, 44, 'hello444');
INSERT INTO t1 VALUES (5, 55, 'hello555');
INSERT INTO t1 VALUES (6, 66, 'hello666');
INSERT INTO t1 VALUES (7, 77, 'hello777');
INSERT INTO t1 VALUES (8, 88, 'hello888');
INSERT INTO t1 VALUES (9, 99, 'hello999');
INSERT INTO t1 VALUES (10, 1010, 'hello101010');
ALTER TABLE t1 ADD INDEX k1(b);
InnoDB 將主鍵字段附加到二級索引。二級索引 k1 的記錄格式為 (b, a)。在排序階段之后,記錄是:
(11,1), (22,2), (33,3), (44,4), (55,5), (66,6), (77,7), (88,8), (99,9), (1010, 10)。
初始插入階段
讓我們從記錄 (11,1) 開始。
- 在 Level 0(葉級別)創建一個頁面。
- 在頁面上創建一個游標。
- 所有插入內容都會轉到此頁面,直到填滿為止。
箭頭顯示游標當前指向的位置。它目前在第 5 頁,下一個數據插入在此頁。
還有兩個空槽,因此插入記錄 (22,2) 和 (33,3) 很簡單。
對于下一條記錄 (44, 4),第 5 頁已滿。以下是步驟。
頁面填滿時的索引構建
- 創建兄弟頁面 – 第 6 頁。
- 暫時不要插入數據到兄弟頁面。
- 提交游標處的頁面,即迷你事務提交、釋放閂鎖等。
- 作為提交的一部分,創建一個節點指針并將其插入到父頁面(current Level(0) + 1) 中。即在 Level 1。
- 節點指針的格式為(子頁中的最小鍵,子頁號)。第 5 頁的最小鍵是 (11,1)。在父級插入記錄 ((11,1),5)。
- Level 1 的父頁面尚不存在。 MySQL 創建第 7 頁和指向第 7 頁的游標。
- 將 ((11,1),5) 插入第 7 頁。
- 現在,返回到 Level 0 并創建從第 5 頁到第 6 頁的鏈接,反之亦然。
- Level 0 的游標現在指向兄弟頁第 6 頁。
- 將 (44,4) 插入第 6 頁。
下一個插入 - (55,5) 和 (66,6) - 很簡單,在第 6 頁。
記錄 (77,7) 的插入與 (44,4) 類似,只是父頁面(第 7 頁)已經存在并且它有空間容納另外兩條記錄。先將節點指針 ((44,4),6) 插入第 7 頁,然后將 (77,7) 記錄到同級第 8 頁中。
插入記錄 (88,8) 和 (99,9) 很簡單,因為第 8 頁有兩個空槽。
下一個插入 (1010, 10)。將節點指針 ((77,7),8) 插入到級別 1 的父頁面(第 7 頁)。
MySQL 在 Level 0 創建兄弟頁 9。將記錄 (1010,10) 插入第 9 頁并將游標更改為該頁。
提交所有級別的游標。在上面的示例中,數據庫在級別 0 提交第 9 頁,在級別 1 提交第 7 頁。我們現在有一個自下而上構建的完整 B+-樹索引!
索引填充因子
全局變量 innodb_fill_factor
設置 Btree
索引頁面中用于插入的空間量。默認值為 100,表示使用整個頁面(不包括頁眉、頁尾)。聚集索引具有 innodb_fill_factor = 100
的豁免。在這種情況下,1/16 的聚集索引頁面空間保持空閑。 也就是說。 6.25% 的空間是為未來的 DML
保留的。
innodb_fill_factor
值 80 意味著 MySQL
使用 80% 的頁面用于插入,并留下 20% 用于將來的更新。
如果 innodb_fill_factor 為 100,則沒有剩余空間可用于將來插入二級索引。如果您希望在添加索引后表上有更多的 DML,DML 可能會導致頁面拆分以及再次合并。在這種情況下,建議使用 80-90 之間的值。此變量值還會影響OPTIMIZE TABLE
或ALTER TABLE DROP COLUMN、ALGORITHM=INPLACE
索引語句的索引重建。
您不應該使用太低的值——例如低于 50——因為索引會占用更多的磁盤空間。值越低,索引中的頁面就越多,索引統計信息采樣可能不是最佳的。優化器可能會選擇具有次優統計信息的錯誤查詢計劃。
排序索引構建的優勢
- 沒有頁面拆分(不包括壓縮表)和合并。
- 沒有重復查找插入位置(頁面反復讀取,IO較多)。
- 插入不會記錄 redo 日志(頁面分配除外),因此重做日志子系統的壓力較小。
缺點
沒有……嗯,好的,有一個,值得單獨發表一篇博文??敬請關注!