问题描述
某TokuDB实例备库发生复制中断,报错信息甚是诡异:
Error executing row event: "Can't lock file (errno: 22 - Invalid argument)"
经过gdb core后,大体知道了发生错误的原因:
TokuDB在创建子事务的时候,由于嵌套事务过多,FT-index直接返回了错误(TokuDB的嵌套事务最多允许256个,嵌套事务数目的类型为uint8_t)导致。
比较诡异的是主库一切正常,无任何错误。
经过沟通,发现用户使用了大量的savepoint,这是一个突破点。
Savepoint机制
在TokuDB里savepoint的机制是个啥样子呢?
这里我们分两种情况来处理,先来看第一种,SQL1:
set autocommit=0;
savepoint s0;
insert into tb1 values(0);
release savepoint s0;
savepoint s1;
insert into tb1 values(1);
release savepoint s1;
...
commit;
TokuDB将会这样处理:
a) savepoint s0:
创建s0子事务
b) release savepoint s0的时候:
commit当前s0事务
c) savepoint s1:
创建s1子事务(此时s0子事务已不存在,已提交)
d) release savepoint s1的时候:
commit当前s1事务
savepoint 间的事务并非嵌套,而是用完就释放,然后再重新创建,这样不会导致事务栈溢出。
再来看另外一种情况,SQL2:
set autocommit=0;
savepoint s0;
insert into tb1 values(0);
savepoint s1;
insert into tb1 values(1);
savepoint s2;
insert into tb1 values(2);
...
commit;
这种SQL TokuDB的处理方式是:
a) savepoint的时候:
s1是s0的子事务,s2是s1的子事务,就这样嵌套下去直到事务栈溢出...
b) commit的时候:
会先提交s2,然后提交s1,然后提交s0,最后整个事务提交
问题解决
针对开始时的问题,TokuDB内核君进行了大胆猜测:
在主库执行的是SQL1,一切正常;
在备库执行的是SQL2,事务栈溢出了;
通过翻看 binlog 代码印证了我们的猜测,在 sql/binlog.cc 里只实现了 binlog_savepoint_set
方法,只记录savepoint event到binlog,而未记录release savepoint event!
由于savepoint实现机制不同,对InnoDB引擎来说是没有问题的,但对 TokuDB 来说就是灾难了。解决办法就是实现binlog_savepoint_release
方法,记录release savepoint event到binlog。
ApsaraDB MySQL 5.6 最新版已修复这个问题,对savepoint有需求的TokuDB用户(InnoDB不受影响)做下升级即可。
本篇小文从一个用户引发的问题出发,讨论了TokuDB的savepoint机制,当然TokuDB内核组的同学每天都会处理很多类似的问题,从发现问题到解决问题,以及持续的优化,为的就是给大家提供一种“飞”的感觉。有大数据量存储的同学不妨试试我们的 TokuDB 引擎,存储成本大大降低,顺便体验一把下一代存储引擎带来的“优越感”(注意,这不是广告)。
最后广告还是来了:
我们的PB级云数据库 PetaData已开始公测,底层采用TokuDB引擎,存储介质是普通的SATA盘,有兴趣的同学可以去围观下。