[MySQL bug] unique key corruption again…..

本文涉及的产品
RDS AI 助手,专业版
RDS DuckDB + QuickBI 企业套餐,8核32GB + QuickBI 专业版
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
简介:
最近Percona的研发人员report了一个uk corruption的bug,这个Bug不同于之前发现的bug(见我的另外一篇博客http://mysqllover.com/?p=1041),而是影响从5.1 到5.8全系列MySQL版本,应该算是设计上的缺陷吧。

创建测试表
CREATE TABLE t1 (      
a INT NOT NULL,      
b INT NOT NULL,      
PRIMARY KEY(b),      
UNIQUE KEY(a)) ENGINE=INNODB;

INSERT INTO t1 VALUES (1,1),(2,2);

 

0. 停止Purge操作:SET GLOBAL innodb_purge_stop_now = ON; 防止标记删除的记录被purge掉
删除表上的数据:DELETE FROM t1;  //这时候表上物理记录还存在,只是被标记删除了。
1. SESSION 1: REPLACE INTO t1 VALUES (1,2);
由于有一条标记删除的记录,检查pk上duplicate key,在聚集索引上加锁:mode=1027 (1024 +3 = LOCK_REC_NOT_GAP | LOCK_X)
堆栈:
row_ins_clust_index_entry 
|--> row_ins_clust_index_entry_low 
	|--> row_ins_duplicate_error_in_clust 
		|--> row_ins_set_exclusive_rec_lock 
			|-->lock_clust_rec_read_check_and_lock
检查二级索引duplicate key,由于存在标记删除的记录,这里需要加锁,类型为X锁
堆栈:
row_ins_index_entry 
|-->row_ins_sec_index_entry 
	|--> row_ins_sec_index_entry_low 
		|--> row_ins_scan_sec_index_for_duplicate 
			|--> row_ins_set_exclusive_rec_lock 
				|--> lock_sec_rec_read_check_and_lock

 

当完成duplicate key检测后 (当然这里是成功的),我们让SESSION1稍微等一会(设置DEBUG SYNC同步点)
2. SESSION 2:REPLACE INTO t1 VALUES (1,3);  
尝试插入第二条记录,这条记录和SESSION1插入的uk是冲突的,pk不冲突,因此先插入pk成功,然后检查uk上的duplicate key加锁:mode =3,但是session1已经在uk为1的记录上加X锁了,因此Session 2进入锁等待
加锁堆栈:
row_ins_index_entry  
|--> row_ins_sec_index_entry 
	|--> row_ins_sec_index_entry_low 
		|--> row_ins_scan_sec_index_for_duplicate 
			|--> row_ins_set_exclusive_rec_lock 
				|--> lock_sec_rec_read_check_and_lock
mysql> select * from information_schema.innodb_locks;
+------------+-------------+-----------+-----------+-------------+------------+------------+-----------+----------+-----------+
| lock_id | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
+------------+-------------+-----------+-----------+-------------+------------+------------+-----------+----------+-----------+
| 1300:6:4:2 | 1300 | X | RECORD | `test`.`t1` | a | 6 | 4 | 2 | 1 |
| 1299:6:4:2 | 1299 | X | RECORD | `test`.`t1` | a | 6 | 4 | 2 | 1 |
+------------+-------------+-----------+-----------+-------------+------------+------------+-----------+----------+-----------+
2 rows in set (0.00 sec)
3. 开启Purge操作:SET GLOBAL innodb_purge_run_now=ON;
后台Purge线程会去清理标记删除的二级索引记录,但之前session1和session2都有请求uk上的排他锁,记录没有了,这些锁对象也需要做对应的处理,理论上锁请求应该被下一条记录锁继承,并转换成GAP锁
堆栈:
row_purge_step 
|--> row_purge 
	|--> row_purge_record_func 
		|--> row_purge_del_mark 
			|--> row_purge_remove_sec_if_poss 
				|--> row_purge_remove_sec_if_poss_leaf 
					|--> btr_cur_optimistic_delete_func 
						|--> lock_update_delete 
							|--> lock_rec_inherit_to_gap
我们来看看锁继承的逻辑,函数为lock_rec_inherit_to_gap:
        for (lock = lock_rec_get_first(block, heap_no);
             lock != NULL;
             lock = lock_rec_get_next(heap_no, lock)) {
                if (!lock_rec_get_insert_intention(lock)
                    && !((srv_locks_unsafe_for_binlog
                          || lock->trx->isolation_level
                          <= TRX_ISO_READ_COMMITTED)
                         && lock_get_mode(lock) == LOCK_X)) {

                        lock_rec_add_to_queue(
                                LOCK_REC | LOCK_GAP | lock_get_mode(lock),
                                heir_block, heir_heap_no, lock->index,
                                lock->trx, FALSE);
                }
        }

 

对于如下场景,不会做锁继承:
a. 锁类型为插入意向锁
b. srv_locks_unsafe_for_binlog打开且锁类型为X锁
c. 锁对应事务的隔离级别小于等于RC且锁类型为X锁
由于在执行类似REPLACE, LOAD DATAFILE REPLACE, INSERT ON DUPLICATE KEY UPDATE等操作时,当检查duplicate key时加的是X锁,本测试样例的隔离级别为RC,因此锁对象未被继承,而是直接解除了,随后等待锁的线程(session2)被唤醒(lock_update_delete –> lock_rec_reset_and_release_wait)。
从IS表也可以看出来这一变化:
mysql> select * from information_schema.innodb_locks;
Empty set (0.00 sec)

 

此时innodb_locks里面已经没有记录了,purge操作“悄悄”的破坏了InnoDB的锁协议。
随后唤醒SESSION2继续操作。
4. SESSION 2: 由于获得了记录锁,因此可以继续插入记录
5. SESSION 1:由于已经完成了duplicate key检查,因此可以继续插入记录
mysql> select * from t1;
+---+---+
| a | b |
+---+---+
| 1 | 2 |
| 1 | 3 |
+---+---+
2 rows in set (0.00 sec)

mysql> check table t1;
+---------+-------+----------+-----------------------------------------------+
| Table   | Op    | Msg_type | Msg_text                                      |
+---------+-------+----------+-----------------------------------------------+
| test.t1 | check | Warning  | InnoDB: The B-tree of index "a" is corrupted. |
| test.t1 | check | error    | Corrupt                                       |
+---------+-------+----------+-----------------------------------------------+
2 rows in set (0.00 sec)
问题及解决
问题的原因Alexey其实在bug上已经解释的很清楚,Innodb认为只可能加S锁来维持一致性约束,因此当记录被物理删除时,只有S类型的锁才被继承。但对于REPLACE这样的操作,加的是X类型的锁,这种锁类型必须也要考虑进去,将其继承给下一条记录。Alexey已经将patch push到percona server,改动也就一行:
  lock_gap
Ref:

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
SQL 关系型数据库 MySQL
MySQL唯一约束(UNIQUE KEY)
MySQL唯一约束(UNIQUE KEY)
1345 0
|
XML 关系型数据库 MySQL
【MySQL异常】ExecutorException: Error getting generated key or setting result to parameter object
【MySQL异常】ExecutorException: Error getting generated key or setting result to parameter object
1201 0
|
关系型数据库 MySQL 数据库
连接MySQL时报错:Public Key Retrieval is not allowed的解决方法
连接MySQL时报错:Public Key Retrieval is not allowed的解决方法
2338 1
|
关系型数据库 MySQL
Mysql 主键冲突(ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY')
Mysql 主键冲突(ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY')
2464 0
|
关系型数据库 MySQL 数据库
MySQL删除全局唯一索引unique
这篇文章介绍了如何在MySQL数据库中删除全局唯一的索引(unique index),包括查看索引、删除索引的方法和确认删除后的状态。
1017 9
|
SQL 关系型数据库 MySQL
(十八)MySQL排查篇:该如何定位并解决线上突发的Bug与疑难杂症?
前面《MySQL优化篇》、《SQL优化篇》两章中,聊到了关于数据库性能优化的话题,而本文则再来聊一聊关于MySQL线上排查方面的话题。线上排查、性能优化等内容是面试过程中的“常客”,而对于线上遇到的“疑难杂症”,需要通过理性的思维去分析问题、排查问题、定位问题,最后再着手解决问题,同时,如果解决掉所遇到的问题或瓶颈后,也可以在能力范围之内尝试最优解以及适当考虑拓展性。
1734 3
|
关系型数据库 MySQL 数据库
Mysqlbug-Could not create or access the registry key needed for the MySQL applicationto, TIMESTAMP w
Mysqlbug-Could not create or access the registry key needed for the MySQL applicationto, TIMESTAMP w
|
关系型数据库 MySQL 数据库
MySQL8报错:Public Key Retrieval is not allowed
MySQL8报错:Public Key Retrieval is not allowed
7939 1
|
关系型数据库 MySQL
MySQL创建表出现 Specified key was too long; max key length is 767 bytes
MySQL创建表出现 Specified key was too long; max key length is 767 bytes
611 2
|
关系型数据库 MySQL
零基础带你学习MySQL—foreign key 外键(二十六)
零基础带你学习MySQL—foreign key 外键(二十六)

推荐镜像

更多