技术分享 | 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,惋惜这步对于绝大多数生产环境来讲都太大了。

相关文章
相关标签/搜索