MySQL:主從HASH SCAN算法可能導致從庫數據錯誤
本文主要以hash scan全表為基礎進行分析,而不涉及到hash scan索引,實際上都會遇到這個問題。本文主要描述的是update event,delete event也是一樣的,測試包含8022,8026,8028均包含這個問題。
約定:bi為update row event的before image
一、問題描述
這里簡單看一下報錯的我們直接用metalink 上的文章來看,實際上作為做oracle的老人,還是比較查metalink的,在metalink上也有一些MySQL相關的文章,但是很少,如下:
錯誤就是那個錯誤,解決辦法也比較簡單就是加上主鍵重做,這個問題我個人已經遇到N次了,每次都這么處理的,隱約的覺得hash scan 有BUG。
二、關于hash scan算法簡介
在8.0中 hash scan 使用一個std::unordered_multimap的hash容器,記錄其key - value值,每個key - value 代表修改的一行值,因為multimap容器允許重復的key - value,因此可以存在相同的行記錄,這和5.7的實現不同,5.7是自己寫的,而8.0 用的容器。其中:
- key為當前表中根據每個字段計算出來的crc32值,句函數為Hash_slave_rows::make_hash_key,也就是checksum_crc32函數
- value為當前本行在event buffer中的位置,也就是指向實際的數據。
當然這里是簡化了,實際value還包含一個std::unordered_multimap的迭代器和刪除器,其中迭代器的作用是通過相同的key 調用,std::next 來查找下一個相同key的記錄。
當一個event掃描結束后會將所有這個event的記錄存儲到這個hash容器中,函數Hash_slave_rows::put。而查找階段會全表掃描本表,每次獲取一行數據,然后在hash容器中進行查找,并進行處理,如下:
->循環1 讀取表中的每條數據
計算本行數據的crc32值,并且在event的hash 結構查找對應的entry
->循環2
拷貝讀取到的行從record0到record1,也就是record1為掃描到的行
->循環3
從查找的entry中獲取bi記錄的位置,并且放入到record0中
比對record0和record1的值是否相等,也就是record0是event對應的bi數據,而record1是掃描的本行數據
如果比對不成功這獲取查找到entry key在event中的下一條記錄
<-循環3結束條件為退出條件為找到了一條匹配記錄或者entry為NULL
->如果查找到對應的entry且比對成功,也就是entry不為NULL
恢復record1到record0中
并且刪除hash 結構中的這個entry
進行數據修改
<-循環2結束條件為再次使用record0也就是掃描的行在event的hash結構查找不到對應的entry,很顯然后面邏輯只要匹配到了就會就會從event的hash結構查找中刪除掉
這樣做的目的很明確就是將全表掃描的次數減少,每個event才做一次,這樣自然提高了性能。
三、BUG登場
這個BUG是同事查詢到后給我的,BUG如下:https://bugs.mysql.com/bug.php?id=101828
在這個BUG中,出現了2行記錄crc32一致的情況,如下2個字符串的crc32也是一致的:
mysql> select crc32("b5a7b602ab754d7ab30fb42c4fb28d82");
+-------------------------------------------+
| crc32("b5a7b602ab754d7ab30fb42c4fb28d82") |
+-------------------------------------------+
| 2575120314 |
+-------------------------------------------+
1 row in set (3.16 sec)
mysql> select crc32("d19f2e9e82d14b96be4fa12b8a27ee9f");
+-------------------------------------------+
| crc32("d19f2e9e82d14b96be4fa12b8a27ee9f") |
+-------------------------------------------+
| 2575120314 |
+-------------------------------------------+
但是在整個hash scan 邏輯中,實際上比對crc32相同過后還是做了實際值的比較,也就是不完全依賴crc32值。這個BUG的流程如下:
第一階段,數據準備階段:
CREATE TABLE t1 (
a bigint unsigned not null,
b bigint unsigned not null
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into t1 values(0xa8e8ee744ced7ca8, 0x6850119e455ee4ed),(0x135cd25c170db910, 0x6916c5057592c796);
這兩行數據的crc32值一樣。
第二階段,數據錯誤階段 主庫執行:
update t1 set a=1 where a=0x135cd25c170db910 and b=0x6916c5057592c796;
顯然這條語句更改是第二行數據,但是到了從庫由于BUG存在更改的是第一條數據,這個時候數據已經錯誤了。這個時候主庫數據如下:
mysql> select * from t1;
+----------------------+---------------------+
| a | b |
+----------------------+---------------------+
| 12171240176243014824 | 7516527149547709677 |
| 1 | 7572456450708129686 |
+----------------------+---------------------+
2 rows in set (0.00 sec)
從庫數據如下:
mysql> select * from t1;
+---------------------+---------------------+
| a | b |
+---------------------+---------------------+
| 1 | 7516527149547709677 |
| 1395221277543610640 | 7572456450708129686 |
+---------------------+---------------------+
2 rows in set (0.00 sec)
第三階段,報錯階段:
update t1 set a=2 where a=1;
這里主庫修改是第二行記錄,也就是:
a=1
b=7572456450708129686
但是到了從庫,因為a=1和b=7572456450708129686的hash crc32值和表中任何一個記錄都不匹配 ,這報錯。
四、原因
來看一下為什么出現問題。 在例子中如果我們修改了1行數據,并且這行數據在表中有2數據存在相同的crc32值,主庫修改的是2行數據,那么可能存在下面的問題:
從庫首先在循環1中獲取第1行數據,然后在hash結構中查找,找到相同的crc32值,進入循環2拷貝record后進入循環3首先對比record0到record1的值也就是event中的數據,也就是第2行數據和第1行數據對比,顯然實際的值肯定不同,這獲取event相同crc32的下一條記錄,顯然不存在因為就更改了1條數據,返回為NULL,循環3結束,繼續,因為entry為NULL,恢復record0的操作和更改數據的操作都不會做。
然后循環2循環條件再次通過掃描到的行數據查找hash結構的entry依舊是第1行數據的entry,進行下一次循環,這個時候因為record0沒有恢復,還是event對應的bi數據,因此拷貝后record1也就是event對應的bi數據,接著進入循環3,這個時候進行比較,實際上比較都是event中的數據,因此比較一定成功,進入修改流程。 這個時候實際上就是把表中的第一行數據給修改了。也就是這個時候數據已經不對了,再次進行修改在錯誤數據上進行修改自然就可能查不到數據的情況。這實際就是一個crc32碰撞后邏輯錯誤導致的問題。
五、總結
- 數據量和本BUG相關,如果數據量大則crc32 不同記錄產生相同crc32的可能性就高一些。
- 本BUG一直未修復,BUG提交者提交了patch,實際上就是當entry為NULL的時候結束循環2,這樣就會掃描表的下一條數據,而不是直接修改本行數據。不知道官方是否覺得BUG中提交的patch不合適,還是其他原因。
- 這個BUG看起來和Bug#28846386: RBR + STORED FUNCTION WITHOUT PRIMARY KEY - CAN'T FIND RECORD IN 有關,可能是修復一個BUG引入的新的BUG,這是8017修復的。
- 8.0 hash scan 已經成為了默認的算法,因此概率大大提高。
- 看來主鍵越來越重要了,有主鍵自然不會觸發這個問題,還是重要事情說三遍吧,加主鍵、加主鍵、加主鍵。