【譯】MySQL InnoDB 排序索引構建

前言

不必了解 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 創建一個中間表。中間表索引(主鍵索引和二級索引)是使用“排序索引構建”構建的。

算法

  1. 在 Level 0創建一個頁面。同時創建一個指向該頁面的游標。
  2. 使用 Level 0 的游標插入數據到頁面,直到填滿。
  3. 一旦頁面已滿,創建一個兄弟頁面(不插入數據到兄弟頁面)。
  4. 為當前滿頁創建一個節點指針(子頁中的最小鍵),并在上一層(父頁)插入一個節點指針。
  5. 在上層,檢查游標是否已經定位。如果沒有,請為該級別創建一個父頁面和一個游標。
  6. 在父頁面插入節點指針。
  7. 如果父頁面已滿,請重復步驟 3、4、5、6。
  8. 現在插入兄弟頁面并使游標指向兄弟頁面。
  9. 在所有插入的末尾,每個級別都有一個指向最右側頁面的游標。提交所有游標(意味著提交修改頁面的小事務,釋放所有鎖存器)。

為簡單起見,上述算法跳過了有關壓縮頁面和處理 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) 開始。

  1. 在 Level 0(葉級別)創建一個頁面。
  2. 在頁面上創建一個游標。
  3. 所有插入內容都會轉到此頁面,直到填滿為止。
(11,1)插入

箭頭顯示游標當前指向的位置。它目前在第 5 頁,下一個數據插入在此頁。

還有兩個空槽,因此插入記錄 (22,2) 和 (33,3) 很簡單。

(22,2) 和 (33,3) 插入

對于下一條記錄 (44, 4),第 5 頁已滿。以下是步驟。

頁面填滿時的索引構建

  1. 創建兄弟頁面 – 第 6 頁。
  2. 暫時不要插入數據到兄弟頁面。
  3. 提交游標處的頁面,即迷你事務提交、釋放閂鎖等。
  4. 作為提交的一部分,創建一個節點指針并將其插入到父頁面(current Level(0) + 1) 中。即在 Level 1。
  5. 節點指針的格式為(子頁中的最小鍵,子頁號)。第 5 頁的最小鍵是 (11,1)。在父級插入記錄 ((11,1),5)。
  6. Level 1 的父頁面尚不存在。 MySQL 創建第 7 頁和指向第 7 頁的游標。
  7. 將 ((11,1),5) 插入第 7 頁。
  8. 現在,返回到 Level 0 并創建從第 5 頁到第 6 頁的鏈接,反之亦然。
  9. Level 0 的游標現在指向兄弟頁第 6 頁。
  10. 將 (44,4) 插入第 6 頁。
(44,4) 插入

下一個插入 - (55,5) 和 (66,6) - 很簡單,在第 6 頁。

(55,5) 和 (66,6)插入

記錄 (77,7) 的插入與 (44,4) 類似,只是父頁面(第 7 頁)已經存在并且它有空間容納另外兩條記錄。先將節點指針 ((44,4),6) 插入第 7 頁,然后將 (77,7) 記錄到同級第 8 頁中。


(77,7) 插入

插入記錄 (88,8) 和 (99,9) 很簡單,因為第 8 頁有兩個空槽。

(88,8) 和 (99,9)插入

下一個插入 (1010, 10)。將節點指針 ((77,7),8) 插入到級別 1 的父頁面(第 7 頁)。

MySQL 在 Level 0 創建兄弟頁 9。將記錄 (1010,10) 插入第 9 頁并將游標更改為該頁。

(1010,10) 插入

提交所有級別的游標。在上面的示例中,數據庫在級別 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 TABLEALTER TABLE DROP COLUMN、ALGORITHM=INPLACE索引語句的索引重建。

您不應該使用太低的值——例如低于 50——因為索引會占用更多的磁盤空間。值越低,索引中的頁面就越多,索引統計信息采樣可能不是最佳的。優化器可能會選擇具有次優統計信息的錯誤查詢計劃。

排序索引構建的優勢

  1. 沒有頁面拆分(不包括壓縮表)和合并。
  2. 沒有重復查找插入位置(頁面反復讀取,IO較多)。
  3. 插入不會記錄 redo 日志(頁面分配除外),因此重做日志子系統的壓力較小。

缺點

沒有……嗯,好的,有一個,值得單獨發表一篇博文??敬請關注!

【原文地址】:MySQL InnoDB Sorted Index Builds

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容