分布式事务锁 mysql_MySQL 分布式事务锁恢复机制探究

本文是我最初于2016底年发表在我的个人微信公众号里面,现发布在这里。

在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层面的表锁不能正确恢复,导致了问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值