ChangeBuffer是InnoDB緩存區的一種特殊的數據結構,當用戶執行SQL對非唯一索引進行更改時,如果索引對應的數據頁不在緩存中時,InnoDB不會直接加載磁盤數據到緩存數據頁中,而是緩存對這些更改操作。這些更改操作可能由插入、更新或刪除操作(DML)觸發。緩存區的更改操作會在磁盤數據被其它讀操作加載到緩存中時合並到對應的緩存數據頁中。
ChangeBuffer
InnoDB ChangeBuffer的官方示意圖如下所示,從圖中可以看出以下信息:
- ChangeBuffer用於存儲SQL變更操作,比如Insert/Update/Delete等SQL語句;
- ChangeBuffer中的每個變更操作都有其對應的數據頁,並且該數據頁未加載到緩存中;
- 當ChangeBufferd中變更操作對應的數據頁加載到緩存中后,InnoDB會把變更操作Merge到數據頁上;
- InnoDB會定期加載ChangeBuffer中操作對應的數據頁到緩存中,並Merge變更操作;
基於個人理解並參考官方的ChangeBuffer示例圖,我繪制了以下更為直觀的的ChangeBuffer示例圖:
ChangeBuffer的作用
我們知道InnoDB推薦使用自增主鍵,插入時主鍵值時遞增的,可以順序訪問。與聚簇索引不同,二級索引通常是不是唯一的,並且以相對隨機的順序插入。類似的,二級索引的更新和刪除經常也會影響索引樹中不相鄰的二級索引數據頁。
對於二級索引數據變更引起的隨機訪問,如果每次都進行磁盤IO顯然會影響數據庫的性能。因此InnoDB不會立即執行數據頁不在緩存中的二級索引的變更操作,而是先將變更操作緩存起來,在某個時刻再將某一個數據頁上面的所有變更操作合並到該數據頁上,通過變更操作緩存(ChangeBuffer)可合並同一個數據頁上的大量隨機訪問I/O。
ChangeBuffer工作流程
變更操作什么時候放入ChangeBuffer
並不是數據庫中的所有操作都會進入ChangeBuffer,滿足以下條件的數據庫語句,在執行階段不會修改數據頁,而是會進入ChangeBuffer,
- SQL會修改數據庫中的數據;
- SQL語句不涉及唯一鍵的校驗;
- SQL語句不需要返回變更后的數據;
- 涉及的數據頁不在緩存中;
ChangeBuffer合並到原數據頁
我們知道,ChangeBuffer中緩存了變更操作,這些操作最終需要合並到數據庫的數據頁,合並過程稱為Merge,那么在什么場景下會觸發ChangeBuffer的Merge操作呢?
- 訪問變更操作對應的數據頁;
- InnoDB后台定期Merge;
- 數據庫BufferPool空間不足;
- 數據庫正常關閉時;
- RedoLog寫滿時;
為什么ChangeBuffer只緩存非唯一索引數據
ChangeBuffer僅僅適用於變更的數據未為非唯一索引的情況,如果變更操作修改的數據為唯一索引或者主鍵數據,那么InnoDB無法把變更操作緩存到ChangeBuffer,這是為什么呢?
以一張用戶表為例,用戶表包含主鍵ID、年齡、姓名和性別四個字段,其中年齡添加了非唯一索引,初始數據及建表語句如下所示:
用戶ID | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
姓名 | 陳爾 | 張散 | 李思 | 王舞 | 趙流 | 孫期 | 周跋 | 吳酒 | 鄭史 |
性別 | 男 | 男 | 女 | 女 | 男 | 男 | 男 | 女 | 男 |
年齡 | 5 | 10 | 20 | 28 | 35 | 56 | 25 | 80 | 90 |
create table user_info
(
id int primary key,
age int not null,
name varchar(16),
sex bool,
key(age)
)engine=InnoDB;
非唯一索引更新
假設我們使用SQL語句update user_info set age=6 where id=1
修改ID=1的用戶的年齡為6,該操作會同時修改年齡索引以及行數據中的年齡,更新步驟如下:
- 如果需要更改的年齡索引頁和行數據頁在緩存中,直接更新緩存中的數據,並把數據頁標記為臟頁;
- 如果需要更改的年齡索引頁和行數據頁不在緩存中,直接把SQL語句
update user_info set age=6 where id=1
存儲到ChangeBuffer;
唯一索引更新
假設我們使用SQL語句update user_info set id=2 where id=1
修改ID=1的用戶的ID為2,該操作會同時修改聚簇索引和行數據,更新步驟如下:
- 如果需要更改的聚簇索引和行數據頁在緩存中,直接更新緩存中的數據,並把數據頁標記為臟頁;
- 如果需要更改的聚簇索引頁和行數據頁不在緩存中,需要把對應的數據頁加載到緩存中,判斷修改之后ID是不是符合唯一鍵約束,然后修改緩存中的數據;
可以看到,由於唯一索引需要進行唯一性校驗,所以對唯一索引進行更新時必須將對應的數據頁加載到緩存中進行校驗,從而導致ChangeBuffer失效。
普通索引還是唯一索引
通過以上分析,我們知道唯一索引無法使用ChangeBuffer,那么我們實際使用過程中應該使用普通索引還是唯一索引呢?
從等值查詢性能角度來看:
- 普通索引在查找到第一個滿足條件的數據之后,需要繼續向后查找滿足條件的數據;
- 唯一索引在查找到第一個滿足條件的數據之后,不需要再次向后查找,因為索引具有唯一性;
二者之間只相差一條記錄,這個一條記錄會帶來多大的性能差距呢?答案是,微乎其微。因為InnoDB引擎是以頁為單位讀取數據的,讀取一條數據時,往往會將臨近的數據也讀到內存,所以多向后查詢幾條數據帶來的性能差別微乎其微。
從索引修改角度來看:
由於非唯一索引無法使用ChangeBuffer,對索引的修改會引起大量的磁盤IO,影響數據庫性能。
綜上可知,如果不是業務中要求數據庫對某個字段做唯一性檢查,我們最好使用普通索引而不是唯一索引。
ChangeBuffer適用場景
什么情況下ChangeBuffer會有較大的性能提升呢?
- 數據庫大部分索引是非唯一索引;
- 業務是寫多讀少,或者不是寫后立刻讀取;
不適合使用ChangeBuffer的場景與之對應:
先說什么時候不適合,如上文分析,當:
- 數據庫都是唯一索引;
- 寫入數據后,會立刻讀取;
ChangeBuffer相關參數
-
innodb_change_buffer_max_size
: 配置寫緩沖的大小,占整個緩沖池的比例,默認值是25%,最大值是50%。
寫多讀少的業務,才需要調大這個值。 -
innodb_change_buffering
: 配置哪些寫操作啟用寫緩沖,可以設置成all/none/inserts/deletes等。
我是御狐神,歡迎大家關注我的微信公眾號:wzm2zsd
本文最先發布至微信公眾號,版權所有,禁止轉載!