MySQL 分布式事务锁恢复机制探究
Posted 数据库开发者
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL 分布式事务锁恢复机制探究相关的知识,希望对你有一定的参考价值。
在 XA PREPARE prepare了一个事务之后,这个事务的日志虽然已经写入了innodb redo log,但是事务锁并没有释放。只有在执行xa commit时候才会释放事务锁。那么如果mysqld 因为crash,被kill掉等原因而重启后,一个事务还能否像刚才mysqld退出之前那样持有当时它当时持有的事务锁呢? 这一点非常关键,如果不能把事务锁的状态正确地恢复的话,那么事务的隔离性就会在重启后被破坏,比如,另一个事务就可以更新正在被更新还未提交的一行。 说实话在亲自验证之前我感觉mysql 可能并不能做到这一点,不过结果非常意外,mysql-5.7.16和mariadb-10.1.9都可以正确地恢复一个innodb 事务的事务锁。当然,由于mariadb-10.1.9对xa事务的支持原本有其他问题,导致它的表现与mysql-5.7.16还是有点差距。另外,MySQL SERVER层面的表锁并不能正确恢复,我已经向Oracle MySQL官方报告了这个bug: http://bugs.mysql.com/bug.php?id=84345
MySQL分布式事务锁恢复功能验证
首先我们的数据是这样的:
mysql> use test;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> show tables;
+----------------+
| Tables_in_test |
+----------------+
| t1 |
| t3 |
+----------------+
2 rows in set (0.00 sec)
mysql> show create table t1;
+-------+-------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+-------------------------------------------------------------------------------------------------------------------+
| t1 | CREATE TABLE `t1` (
`a` int(11) DEFAULT NULL,
`b` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
+-------+-------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> select *from t1;
+------+------+
| a | b |
+------+------+
| 1 | 3 |
| 3 | 2 |
| 2 | 4 |
| 100 | 100 |
| 200 | 100 |
| 0 | 100 |
| 300 | 100 |
| 400 | 400 |
| 500 | 500 |
| 600 | 600 |
| 60 | 60 |
| 660 | 660 |
| 1 | 1 |
| 44 | 44 |
| 55 | 55 |
+------+------+
15 rows in set (0.00 sec)
mysql> show create table t3;
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| t3 | CREATE TABLE `t3` (
`a` tinyint(4) NOT NULL AUTO_INCREMENT,
`b` int(11) DEFAULT NULL,
PRIMARY KEY (`a`)
) ENGINE=InnoDB AUTO_INCREMENT=127 DEFAULT CHARSET=latin1 |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> select*from t3;
+-----+------+
| a | b |
+-----+------+
| -1 | 4 |
| 22 | 7 |
| 127 | 3 |
+-----+------+
3 rows in set (0.00 sec)
测例1. 验证在Xa commit之前,不释放事务锁。
测例2. Xa prepare之后退出会话,不会丢失事务锁。
测例3. Kill掉mysqld 后 检查事务锁恢复是否正确
再来一个insert的测例。
MySQL XA事务锁恢复的实现机制研究
那么这个事务锁恢复功能是如何实现的呢,我做了如下3中猜测。在深入到代码之前,我打算使用一些测例来验证和实验。
1. 通过innodb undo日志和/或 redo日志恢复
由于从innodb的undo 和redo日志可以知道一个事务插入、删除、更新了那些行,那么对这些行获取行锁即可。为了验证这一个想法,特地做了测例4,结果分析见下文。
2. 直接锁住全表
这么做不太可能,因为如果有多个prepared事务更新了同一个表,那么就无法让每个事务获取互斥表锁。而如果不针对事务直接锁表(引用计数),则与通常的事务处理方法非常不同。
为了验证这个想法,特地做了测例5,结果分析见下文。
3. 在xa prepare写redo log时,将这个innodb事务的所有事务锁也记录到redo log中;做恢复的时候,直接恢复这些事务锁。
经过几轮测试分析,我认为这正是innodb实现事务锁恢复的方法。
测例4. 未更新的行的行锁恢复
由于t1表没有主键,所以执行更新语句会锁住每一行。左边事务T1执行一个分布式事务,更新t1的一行R1。在右边的事务T2中更新t1的不同行R2。T1的更新语句做了全表扫导致阻塞其他行的更新。Kill掉mysqld然后重启恢复后,看是否只恢复了被更新的行的行锁还是恢复了所有扫过的行的行锁。
从结果来看,右边的事务 T2 虽然更新的是另一行但是mysqld重启后仍然被左边的PREPARED状态的事务T1阻塞了,说明T1在mysqld被kill掉之前持有的所有事务锁都被恢复了,所以innodb应该不会是使用undo/redo log中记录的这个事务insert/update/delete的数据行来恢复事务锁的。不过,有没有可能是在恢复后,这个表t1完全被锁定了(T1持锁),果真如此的话也会是上述行为。下面这个测例可以回答这个问题。
测例5. 恢复的是表级锁还是行级锁
左边的事务T1 更新有主键的表的一行R1,避免全表扫描锁住所有行。右边的事务T2更新另外一行R2,没有锁冲突直接返回;然后更新R1,发生锁等待。
然后在mysqld被kill掉然后重启恢复后,在右边会话中首先更新不同于R1的一行R3,仍然没有锁冲突直接返回,说明innodb恢复出来的T1的事务锁是行锁而不是表锁。并且重新执行更新R1仍然会被阻塞,符合预期。
下面这个测例更加直接,同时也发现了MySQL-5.7.16的一个BUG,我已经向Oracle MySQL官方报告了这个bug: http://bugs.mysql.com/bug.php?id=84345
总结
Innodb事务锁的恢复是innodb自身完成的,只要执行了xa prepare,那么就可以正确地恢复事务锁。XA事务分支的事务锁在mysqld运行期间可以正确保持,如果在prepare之后因为各种原因导致mysqld重启恢复,那么Innodb里面的事务锁可以正确的恢复。但是MySQL SERVER层面的表锁不能正确恢复,导致了问题。
以上是关于MySQL 分布式事务锁恢复机制探究的主要内容,如果未能解决你的问题,请参考以下文章