技術分享 | HASH_SCAN BUG 之迷惑行爲大賞

原創: 胡呈清sql


前言

咱們知道,MySQL 有一個老問題,當表上無主鍵時,那麼對於在該表上作的DML,若是是以ROW模式複製,則每個行記錄前鏡像在備庫均可能產生一次全表掃描(或者二級索引掃描),大多數狀況下,這種開銷都是很是不可接受的,而且產生大量的延遲。app

在MySQL 5.6 中提供了一個新的參數:slave_rows_search_algorithms, 能夠部分解決無主鍵表致使的複製延遲問題,其基本思路是對於在一個ROWS EVENT中的全部前鏡像收集起來,而後在一次掃描全表時,判斷HASH中的每一條記錄進行更新。優化

以上摘自印風大神的文章,具體效果和使用方式能夠查看文章: https://yq.aliyun.com/articles/41058。spa

 

HASH_SCAN BUG 的迷惑行爲線程

本文不是要繼續討論 slave_rows_search_algorithms 的原理,而是在使用 slave_rows_search_algorithms 參數時遇到一個坑,撲朔迷離的地方在於這不像一個bug,這又是一個bug,最後官方的操做更是讓人看不懂。orm

問題描述索引

row-based replication,主從數據一致狀況下,slave sql 線程報錯 Can't find record。ip

如何復現ci

配置:文檔

slave_rows_search_algorithms = 'INDEX_SCAN,HASH_SCAN'

binlog_format = ROW

在主庫上:

1. CREATE TABLE t1 ( A INT UNIQUE KEY, B INT ); insert into t1 values (1,2);

2. replace into t1 values (1,3),(1,4);

3. 而後從庫就會出現報錯了。

4. 在從庫上:set global slave_rows_search_algorithms='INDEX_SCAN,TABLE_SCAN'; start slave; 問題解決

或者用以下方法避免:

1. 將 UNIQUE KEY 調整爲 PRIMARY KEY;

2. 將 replace into tt.t1 values (1,3),(1,4); 調整爲 replace into tt.t1 values (1,3);replace into tt.t1 values (1,4);

 

分析過程:

查看 slaverowssearch_algorithms 參數定義:

The default value is INDEX_SCAN,TABLE_SCAN, which means that all searches that can use indexes do use them, and searches without any indexes use table scans.

To use hashing for any searches that do not use a primary or unique key, set INDEX_SCAN,HASH_SCAN. Specifying INDEX_SCAN,HASH_SCAN has the same effect as specifying INDEX_SCAN,TABLE_SCAN,HASH_SCAN, which is allowed.

Do not use the combination TABLE_SCAN,HASH_SCAN. This setting forces hashing for all searches. It has no advantage over INDEX_SCAN,HASH_SCAN, and it can lead to 「record not found」 errors or duplicate key errors in the case of a single event containing multiple updates to the same row, or updates that are order-dependent.

(1)根據手冊描述,能夠理解爲:

從庫定位數據可選項有 INDEX_SCAN、HASH_SCAN、TABLE_SCAN,優先級是依次遞減的。也就是說若是有主鍵,走 INDEX_SCAN,沒主鍵則根據設置走 HASH_SCAN 仍是 TABLE_SCAN(並且若是二者都配了,則優先 HASH_SCAN); 無主鍵設置 INDEX_SCAN,HASH_SCAN,能夠提高從庫回訪效率,下降延遲; 若是走了 HASH_SCAN,當對同一行數據同時更新屢次時,會致使沒法找到行記錄。 看到這裏,手冊已經說明緣由了。可是矛盾的地方在於手冊有推薦使用 INDEX_SCAN,HASH_SCAN 的意思,而且 8.0.2 版本開始的默認值已經修改成:INDEX_SCAN,HASH_SCAN

(2)針對上面的疑問提交SR

Oracle 工程師迴應這是 HASH_SCAN 的 bug,修復到了 MySQL8.0.17(尚未正式發佈):

It happens when one row is updated twice within an Update_rows_log_event. HASH_SCAN will put both rows in a hash map. Then it iterates over the rows of the table, looks up each row in the hash. If any row is found, it applies the update. Since it only makes one lookup per row, it will miss the second update. In the end, it checks that all rows in the hash were applied and generates an error otherwise. This is what is seen in the bug report.

 

等等,爲何只修復到 8.0 而沒在 5.7 修復?再根據以前就有的疑問,我來腦補一波:

1. 文檔寫了在某些狀況 HASH_SCAN 有這個問題(吐槽:HASH_SCAN 就是優化無主鍵狀況從庫複製效率的,可是無主鍵且對同一行數據同時更新屢次時 HASH_SCAN 又會致使從庫沒法找到記錄,那我用仍是不用呢?黑人問號.gif),因此暫時我先假設「這不是個 bug」;

2. Oracle 工程師反手甩給我一個 bug 報告,啪啪打臉,官方認可這就是一個 bug;

3. 官方好像也認爲有點不對勁,一拍腦殼,我們只在 8.0 修復,把鍋甩給 「8.0.2 的默認值修改爲了 HASH_SCAN」。

這個迷惑行爲能夠用一句經典總結:it's not a bug,it's a feature!

 

解決方案

1. 給表添加主鍵(規範必須有主鍵纔是王道);

2. 修改參數 slave_rows_search_algorithms='INDEX_SCAN,TABLE_SCAN';

3. 最符合初衷的作法是升級到 8.0.17,惋惜這步對於絕大多數生產環境來講都太大了。

相關文章
相關標籤/搜索