天天看點

MySQL普通索引和唯一索引到底什麼差別?(下)6 change buffer 和 redo log7 總結問題思考

6 change buffer 和 redo log

WAL 提升性能的核心機制,也是盡量減少随機讀寫,它們有啥差別呢?

插入流程

insert into t(id,k) values(id1,k1),(id2,k2);      

假設目前k索引樹的狀态,查找到位置後:

  • k1所在資料頁在記憶體(InnoDB buffer pool)
  • k2資料頁不在記憶體

看如下流程:

帶change buffer的更新流程

圖中箭頭都是背景操作,不影響更新的響應。
MySQL普通索引和唯一索引到底什麼差別?(下)6 change buffer 和 redo log7 總結問題思考

該更新做了如下操作:

Page1在記憶體,直接更新記憶體

Page2不在記憶體,就在change buffer區,緩存一個“往Page2插一行記錄”的資訊

将前兩個動作記入redo log

之後事務完成。執行該更新語句成本很低,隻寫兩處記憶體,然後寫一處磁盤(前兩次操作合在一起寫了一次磁盤),還是順序寫。

處理之後的讀請求

select * from t where k in (k1, k2);      

讀語句緊随更新語句,記憶體中的資料都還在,是以此時這倆讀操作就與系統表空間和 redo log 無關。

帶change buffer的讀過程

MySQL普通索引和唯一索引到底什麼差別?(下)6 change buffer 和 redo log7 總結問題思考

讀Page1時,直接從記憶體傳回。

WAL之後如果讀資料,是不是一定要讀盤,是不是一定要從redo log把資料更新之後才可以傳回?

其實不用。看上圖狀态,雖然磁盤上還是之前資料,但這裡直接從記憶體傳回結果,結果正确。

要讀Page2時,需把Page2從磁盤讀入記憶體,然後應用change buffer裡的記錄檔,生成一個正确版本并傳回結果。可見直到需讀Page2時,該資料頁才被讀入記憶體。

綜上,這倆機制的更新性能:

redo log 主要節省随機寫磁盤的I/O消耗(轉成順序寫)

change buffer主要節省随機讀磁盤的I/O消耗

7 總結

由于唯一索引用不了change buffer,若業務可以接受,從性能角度,優先考慮非唯一索引。

到底何時使用唯一索引

問題在于“業務可能無法確定”。本文前提是“業務代碼已經保證不會寫入重複資料”,才讨論性能問題。

如果業務不能保證或業務就是要求資料庫來做限制

沒得選,必須建立唯一索引。那本文意義的在于,如果碰上大量插入資料慢、記憶體命中率低時,多提供了一個排查思路。

“歸檔庫”場景,可考慮使用唯一索引

比如,線上資料隻需保留半年,然後曆史資料儲存在歸檔庫。此時,歸檔資料已是確定沒有唯一鍵沖突。要提高歸檔效率,可考慮把表的唯一索引改普通索引。

若某次寫入使用了change buffer,之後主機異常重新開機,是否會丢失change buffer資料

不會丢失。

雖然是隻更新記憶體,但在事務送出時,change buffer的操作也被記錄到了redo log。

是以崩潰恢複時,change buffer也能找回。

merge時是否會把資料直接寫回磁盤

merge流程

  1. 從磁盤讀入資料頁到記憶體(老版本資料頁)
  2. 從change buffer找出該資料頁的change buffer 記錄(可能多個),依次應用,得到新版資料頁
  3. 寫redo log

    該redo log包含資料的變更和change buffer的變更

至此merge結束。

這時,資料頁和記憶體中change buffer對應磁盤位置都尚未修改,是髒頁,之後各自刷回自己實體資料,就是另外一過程。

問題思考

在構造第一個例子的過程,通過session A的配合,讓session B删除資料後又重新插入一遍資料,然後就發現explain結果中,rows字段從10001變成37000多。

而如果沒有session A的配合,隻是單獨執行delete from t 、call idata()、explain這三句話,會看到rows字段其實還是10000左右。這是什麼原因呢?

如果沒有複現,檢查

隔離級别是不是RR(Repeatable Read,可重複讀)

建立的表t是不是InnoDB引擎

為什麼經過這個操作序列,explain的結果就不對了?

delete 語句删掉了所有的資料,然後再通過call idata()插入了10萬行資料,看上去是覆寫了原來10萬行。

但session A開啟了事務并沒有送出,是以之前插入的10萬行資料是不能删除的。這樣,之前的資料每行資料都有兩個版本,舊版本是delete之前資料,新版本是标記deleted的資料。

這樣,索引a上的資料其實有兩份。

不對啊,主鍵上的資料也不能删,那沒有使用force index的語句,使用explain指令看到的掃描行數為什麼還是100000左右?(潛台詞,如果這個也翻倍,也許優化器還會認為選字段a作為索引更合适)

是的,不過這個是主鍵,主鍵是直接按照表的行數來估計的。而表的行數,優化器直接用的是show table status的值。

大家的機器如果IO能力比較差的話,做這個驗證的時候,可以把innodb_flush_log_at_trx_commit 和 sync_binlog 都設成0。

參考

https://dev.mysql.com/doc/refman/8.0/en/innodb-change-buffer.html