最近在網上看到一些文章裡說:「change buffer 只適用於非唯一索引頁。」其實這個觀點是錯的,先來看看官方檔案對 change buffer 的介紹:
檔案地址:https://dev.mysql.com/doc/refman/8.0/en/innodb-change-buffer.html
The change buffer is a special data structure that caches changes to secondary index pages when those pages are not in the buffer pool.
這裡的意思是,快取那些不在 buffer pool 中的二級索引頁,並不是指非唯一的二級索引。那具體使用 change buffer 的條件是什麼?其實具體使用條件主要彙集在以下五點:
使用者設定選項 innodb_change_buffering。
在 mysql 的索引結構中,只有葉子結點才儲存資料。因此有葉子節點才考慮是否使用 ibuf。
如上面檔案顯示的一樣,change buffer 只能快取二級索引頁,所以對於聚集索引,不可以快取操作。聚簇索引頁是由 Innodb 引擎將資料頁載入到 Buffer Pool中(這個查詢過程是順序 I/O),然後進行資料記錄插入或者更新、刪除。
因為唯一二級索引(unique key)的索引記錄具有唯一性,因此無法快取插入和更新操作,但可以快取刪除操作;
表上沒有 flush 操作,例如執行 flush table for export 時,不允許對錶進行 ibuf 快取 (通過 dict_table_t::quiesce 進行標識)
接下來我們結合原始碼和檔案來看看具體操作。
原始碼地址:GitHub - mysql/mysql-server: MySQL Server, the world's most popular open source database, and MySQL
先看第一點設定選項尾 innodb_change_buffering,它能夠針對三種型別的操作 INSERT、DELETE-MARK 、DELETE 進行快取,三者對應 dml 語句關係如下:
INSERT 操作:插入二級索引。
先進行 DELETE-MARK 操作,再進行INSERT操作:更新二級索引。
DELETE-MARK 操作:刪除二級索引
// 程式碼路徑:storage/innobase/include/ibuf0ibuf.h
/* Possible operations buffered in the insert/whatever buffer. See
ibuf_insert(). DO NOT CHANGE THE VALUES OF THESE, THEY ARE STORED ON DISK. */
typedef enum {
IBUF_OP_INSERT = 0,
IBUF_OP_DELETE_MARK = 1,
IBUF_OP_DELETE = 2,
/* Number of different operation types. */
IBUF_OP_COUNT = 3
} ibuf_op_t;
/** Combinations of operations that can be buffered.
@see innodb_change_buffering_names */
enum ibuf_use_t {
IBUF_USE_NONE = 0,
IBUF_USE_INSERT, /* insert */
IBUF_USE_DELETE_MARK, /* delete */
IBUF_USE_INSERT_DELETE_MARK, /* insert+delete */
IBUF_USE_DELETE, /* delete+purge */
IBUF_USE_ALL /* insert+delete+purge */
};
此外 innodb_change_buffering 還可以通過設定其他選項來進行相應的快取操作:
all:預設值,預設開啟 buffer inserts、delete-marking operations、purges。
none:不開啟 change buffer。
inserts:只是開啟 buffer insert 操作。
deletes:只是開 delete-marking 操作。
changes:開啟 buffer insert 操作和 delete-marking 操作。
purges:對只是在後臺執行的物理刪除操作開啟 buffer 功能。
第二點還需要大家進行判斷條件即可,所以就不進行擴充套件講解了,我們來細說一下第三點。
第三點的具體參考函數為 ibuf_should_try,它滿足 ibuf 快取條件後,會使用兩種模式去嘗試獲取資料頁。
這裡說明一下,在 MySQL5.5 之前的版本中,由於只支援快取 insert 操作,所以最初叫做 insert buffer,只是後來的版本中支援了更多的操作型別快取,才改叫 change buffer,但是程式碼中與 change buffer 相關的 函數或變數還是以 ibuf 字首開頭。
下面是函數的具體實現,地址在:storage/innobase/include/ibuf0ibuf.ic
/** A basic partial test if an insert to the insert buffer could be possible and
recommended. */
static inline ibool ibuf_should_try(
dict_index_t *index, /*!< in: index where to insert */
ulint ignore_sec_unique) /*!< in: if != 0, we should
ignore UNIQUE constraint on
a secondary index when we
decide */
{
return (innodb_change_buffering != IBUF_USE_NONE && ibuf->max_size != 0 &&
index->space != dict_sys_t::s_dict_space_id &&
!index->is_clustered() && !dict_index_is_spatial(index) &&
!dict_index_has_desc(index) &&
index->table->quiesce == QUIESCE_NONE &&
(ignore_sec_unique || !dict_index_is_unique(index)) &&
srv_force_recovery < SRV_FORCE_NO_IBUF_MERGE);
}
上面加粗和標紅的地方就是對唯一二級索引的判斷的地方,意思是:
當 ignore_sec_unique 這個變數為 0 時,如果修改的是唯一二級索引記錄,就不能使用。
當 ignore_sec_unique 這個變數為 1 時,如果修改的是唯一二級索引記錄,還可以試著使用一下。
ignore_sec_unique 的取值在:storage/innobase/btr/btr0cur.cc
if (btr_op != BTR_NO_OP &&
ibuf_should_try(index, btr_op != BTR_INSERT_OP)) {
/* Try to buffer the operation if the leaf
page is not in the buffer pool. */
fetch = btr_op == BTR_DELETE_OP ? Page_fetch::IF_IN_POOL_OR_WATCH
: Page_fetch::IF_IN_POOL;
}
其中紅框中的 btr_op 指的是本次修改的具體操作是什麼,也就是:
當此次具體的修改操作是 INSERT 操作時,ignore_sec_unique 為 0,也就是當修改的是唯一二級索引記錄時,不可以使用 ibuf。
當此次具體的修改操作不是 INSERT 操作時,ignore_sec_unique 為 1,也就是當修改的是唯一二級索引記錄時,可以試著使用 ibuf。
可以看到當 ibuf_should_try 函數返回 1 時,也就是可以試著用一下 ibuf,那麼就把讀取 buffer pool 中頁面的模式改一下。
最後總結一下,看完了本文相信大家都能比較明確的意識到,網上說只有非唯一索引才能使用 change buffer 的說法,毫無疑問是錯的。只要滿足了其它 4 個條件,對唯一索引進行的刪除操作完全可以使用 change buffer 優化。