InnoDB 按主鍵順序全表掃描時,若主鍵頁碎片化嚴重 (如 PRIMARY KEY (user_id, object_id) 隨機插入導致頁分裂),物理讀取變為**隨機 I/O**,HDD 上性能極差。Facebook 工程團隊在 InnoDB 內核實現 **Logical Read Ahead (LRA, 邏輯預讀)** 與 **批量提交異步 I/O**,在生產環境將 mysqldump、pt-online-schema-change 等全表掃描場景提速 **9-20 倍**。本文深度解析原理、實現、基準測試與開源補丁。
核心思想:利用 B+Tree 非葉子頁 (Branch Pages) 記錄的葉子頁指針,**按邏輯頁號順序批量預讀葉子頁**,再按主鍵順序返回行。
innodb_lra_size 控制每批頁數 (默認 512MB-4GB 內存預算),innodb_lra_sleep 控制批次間休眠 (默認 50ms) 避免沖刷正常業務頁。SET SESSION innodb_lra_size = 536870912; -- 512MB
SET SESSION innodb_lra_sleep = 50000; -- 50ms (微秒)
-- 執行全表掃描
SELECT * FROM huge_table INTO OUTFILE '/tmp/dump.txt';
-- 或 mysqldump 內部自動啟用
問題:InnoDB 5.6+ 使用 Linux Native AIO (io_submit),但**每次僅提交 1 頁 (16KB) 請求**,內核無法合併,IOPS 浪費。
修復:調用 io_submit 前**攒批多個連續頁請求** (如 64 頁 = 1MB) 一次提交。
效果 (RAID 1+0 HDD):
內核合併連續請求為大塊 I/O,極大提升磁盤順序讀效率。
| 場景 | 環境 | 優化前 | 優化後 (LRA + 批量 AIO) | 提速比 |
|---|---|---|---|---|
| 純 HDD 全表掃描 (無其他負載) | 生產碎片表 | ~50 MB/s | ~450 MB/s | 9x |
| Online Schema Change (重負載下) | 生產高峰期 | ~30 MB/s | ~600 MB/s | 20x |
| mysqldump 備份 | 混合負載 | 數小時 | 數十分鐘 | 10-15x |
**關鍵收穫**:LRA 解決「讀什麼」、批量 AIO 解決「怎麼讀」,兩者正交疊加,效果乘法級疊加。
Facebook 已將增強提交至 MySQL 5.6 分支開源:
適用於 MySQL 5.6 / Percona Server 5.6 / MariaDB 10.0+。MySQL 5.7/8.0 架構變化較大,需移植適配 (建議關注社區 innodb_parallel_read_threads 等新特性)。
適用:
mysqldump --single-transaction 邏輯備份SELECT ... INTO OUTFILE 導出pt-online-schema-change / gh-ost 拷貝階段ALTER TABLE ... ENGINE=InnoDB 重建表不適用/無效**:
innodb_lra_size/sleep)。Facebook 通過**邏輯層面順序化 (LRA)** + **系統調用層面批量化 (批量 AIO)**,以極小內核改動 (約 200 行) 解決了困擾 InnoDB 多年的「碎片表全表掃描慢」難題。**期望 Oracle 官方 / Percona / MariaDB 將其納入主線**,或在 8.0 中通過 innodb_parallel_read_threads、innodb_read_ahead_threshold 動態自適應等機制原生解決。在此之前,大表備份/變更慢的團隊可考慮打補丁編譯專用版本,或升級至 8.0 利用並行讀線程特性。