MySQL:主从HASH SCAN算法可能导致从库数据错误

2023-06-06  本文已影响0人  重庆八怪

本文主要以hash scan全表为基础进行分析,而不涉及到hash scan索引,实际上都会遇到这个问题。本文主要描述的是update event,delete event也是一样的,测试包含8022,8026,8028均包含这个问题。
约定:bi为update row event的before image


一、问题描述

这里简单看一下报错的我们直接用metalink 上的文章来看,实际上作为做oracle的老人,还是比较查metalink的,在metalink上也有一些MySQL相关的文章,但是很少,如下:


d5d7810ff10ac627f105e0e6a509a53.jpg

错误就是那个错误,解决办法也比较简单就是加上主键重做,这个问题我个人已经遇到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 用的容器。其中

当然这里是简化了,实际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如下:

在这个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中的数据,因此比较一定成功,进入修改流程。
这个时候实际上就是把表中的第一行数据给修改了。也就是这个时候数据已经不对了,再次进行修改在错误数据上进行修改自然就可能查不到数据的情况。

五、总结

上一篇 下一篇

猜你喜欢

热点阅读