Change Buffer 只適用於非唯一索引頁?錯

2022-05-26 12:00:39

最近在網上看到一些文章裡說:「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 優化。

推薦閱讀

javaScript 記憶體管理機制

130 行程式碼搞定核酸統計,程式設計師在抗疫期間的大能量