August 26, 2013

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的逻辑类似了。 […]