InnoDB: Failing assertion: trx->isolation_level == TRX_ISO_READ_UNCOMMITTED

最近再次碰到之前遇到的断言失败的bug,错误信息如下:

InnoDB: Failing assertion: trx->isolation_level == TRX_ISO_READ_UNCOMMITTED

 

这是一个已知的bug(bug#62037),MySQL5.5.22版本中被fix掉,在lauchpad上可以看到具体是如何修复的

 

如何重现

重现case,使用gdb的non-stop模式很容易重现:

启动gdb,使用non-stop

set target-async 1

set pagination off

set non-stop on



断点:

row0upd.c:2033   (Percona Server5.5.18)



case:



session 1:

 CREATE TABLE `t1` (   `a` int(11) DEFAULT NULL,   `b` text,   `c` text ) ENGINE=InnoDB DEFAULT CHARSET=gbk;

insert into t1 values (1,repeat(‘b’, 7000), repeat(‘c’, 100));

update t1 set c = concat(c, repeat(‘c’, 2000));

这时候会因为更新列c而导致b列的数据被外部存储(b的列长最大),停在断点

session 2:执行查询

select * from t1 where a = 1;

该bug主要包含两个问题:


问题一:运行时断言失败
主要原因是:
1.更新记录时,undo中只记录了那些被更新的列,而由于其他列更新,导致某个列需要外部存储时,这个列的值不会写入undo。
也就是说,在从函数btr_cur_pessimistic_update中返回后,记录上某个未被更新的列,可能存储的是无效的指针
例如上述场景b列被选出来外部存储,因为它的长度最大,因此b列的数据被修改成一个尚未生效的值全为0的指针;
在悲观更新返回后,由于先mtr commit,再更新外部存储数据,这导致block及索引上的排他锁都被释放掉;这时候其他连接的查询是可以看到这条记录的。
2.虽然MVCC保证查询可以看到修改过的列,但1提到的未修改但被选作外部存储的列没有做undo,因此查询看到的是指针,导致触发断言失败,实例crash
解决:
官方的解决办法是在悲观更新和更新完外部存储列后,才进行mtr commit,这可以保证中间不会有其他查询看到未完成的更新(被阻塞住)


问题二:断言失败crash后,无法crash recovery
原因:
如果在完成悲观更新和写入外部存储列的过程中crash,记录更新可能完成了,但外部存储列是失败的,在crash recovery后,记录依然维持在不一致的状态。这样只要一访问该记录,就会报和问题一一样的断言crash错误
解决:

将上述逻辑修改为:

1.对原记录进行修改,mtr不提交(用btr_mtr代表该mtr)
2.扩展新的外部存储页(不能使用btr_mtr之前释放掉的page,暂时没搞明白为什么…),对新的外部存储页的写入(包括初始化)使用另外一个mtr(称为blob_mtr),对记录上指针的更新使用btr_mtr
3.commit blob_mtr
4.commit btr_mtr


这样在crash recovery的时候,就会先恢复blob页,再恢复记录上的操作;最差的情况就是丢失更新,ibd中存在一个有部分数据的无效blob页,但记录本身仍然能保持一致的状态

 

另外在插入记录时也可能触发该bug,例如,当一条记录被标记删除,但未被purge掉时,如果再次插入该聚集索引记录,被标记删除的记录就会被更新成现在的记录,这时候触发bug的逻辑就和上述Update的逻辑类似了。

 

原创文章,转载请注明: 转载自Simple Life

本文链接地址: InnoDB: Failing assertion: trx->isolation_level == TRX_ISO_READ_UNCOMMITTED

Post Footer automatically generated by wp-posturl plugin for wordpress.


Comments

Leave a Reply

Your email address will not be published. Name and email are required


Current month ye@r day *