August 2013

InnoDB: Failing assertion: page_get_n_recs(page) > 1

我们知道,在MySQL5.5里,insert buffer换了个说法,叫change buffer,能够缓存对二级索引的操作,直到将实际的page读入bp时才进行合并,这在IO-bound并且表上有很多二级索引时,可以有效提升性能。 但存在一个蛋疼的bug,在5.5.31版本才被彻底fix掉.如果你不幸碰到如下断言错误crash,那么恭喜你中招了:                 InnoDB: Failing assertion: page_get_n_recs(page) > 1 这个问题最初可以追溯到2012年1月份的中旬,春节前三四天,当时一个线上库不幸触发该bug,导致crash,并且无法重启。 当时的处理方式是用innodb froce recovery起来,同时关掉innodb purge thread(另外一个bug,设置一个较大的innodb force recovery无法启动mysqld),然后dump数据,重建库.. 比较早的关于该bug的讨论见:http://bugs.mysql.com/bug.php?id=61104, 但bug#61104并没用完全修复该bug,只是将断言移除了而已,这样用户可以把实例起来,执行一次DDL来重建表的二级索引 后来Percona的Alexey Kopytov 在buglist上提出了该bug的导致的根本原因(http://bugs.mysql.com/bug.php?id=66819)在于删除ibuf记录和应用Ibuf记录并不是原子的,也就是不在一个mtr中,那么在不恰当的时间点挂掉,就可能导致无法crash recovery,实际上,即使我们将innodb_change_buffer设置inserts也不是安全的。。。。 再后来,这个bug被fix掉了,但我看了diff后,发现fix的不完整,DELETE的场景依然存在问题。于是俺在Facebook上吐槽了下,Percona的Valeriy Kravchuk很热心的帮忙确认了.. 主要涉及两个函数 >ibuf_merge_or_delete_for_page,是对一个block上记录进行change buffer合并的主要函数; 对于Purge操作,即IBUF_OP_DELETE类型: 先执行ibuf_delete后,会直接进行ibuf_btr_pcur_commit_specify_mtr,提交redo,然后再去删除ibuf记录(ibuf_delete_rec) >ibuf_delete_rec,每完成一次change buffer记录的合并,都会调用该函数去从Ibuf tree中将其删除 在ibuf_delete_rec函数中,当进行btr_cur_optimistic_delete失败后,会先去commit mitr(调用ibuf_btr_pcur_commit_specify_mtr),再去开启新的mtr做btr_cur_pessimistic_delete 我们知道,Innodb是通过mtr写入的redo日志来做crash recovery的,如果我们在merge数据成功和删除ibuf记录之间crash掉,那么就可能数据记录被更新了,但ibuf记录还没被删除,从而触发前面提到的断言失败(在做ibuf_delete时,想删除记录,却发现page上的记录已经删光了…) 也就是说,实际上对于所有的change buffer操作类型都可能存在问题,只是对于purge操作,概率更高点,因为它有两个风险点 官方第一次fix,在MySQL5.5.29里,修复了ibuf_delete_rec中存在的问题,方式是先标记删除ibuf entry,再做悲观删除 http://bazaar.launchpad.net/~mysql/mysql-server/5.5/revision/3979 官方第二次fix,在MySQL5.5.31里,修复ibuf_merge_or_delete_for_page中存在的问题 http://bazaar.launchpad.net/~mysql/mysql-server/5.5/revision/4177 原创文章,转载请注明: 转载自Simple Life 本文链接地址: […]

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