我们知道,在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,再做悲观删除
官方第二次fix,在MySQL5.5.31里,修复ibuf_merge_or_delete_for_page中存在的问题