问题描述

某TokuDB实例备库发生复制中断,报错信息甚是诡异:

  1. 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

  1. set autocommit=0;
  2. savepoint s0;
  3. insert into tb1 values(0);
  4. release savepoint s0;
  5. savepoint s1;
  6. insert into tb1 values(1);
  7. release savepoint s1;
  8. ...
  9. commit;

TokuDB将会这样处理:

  1. a) savepoint s0
  2. 创建s0子事务
  3. b) release savepoint s0的时候:
  4. commit当前s0事务
  5. c) savepoint s1
  6. 创建s1子事务(此时s0子事务已不存在,已提交)
  7. d) release savepoint s1的时候:
  8. commit当前s1事务

savepoint 间的事务并非嵌套,而是用完就释放,然后再重新创建,这样不会导致事务栈溢出。

再来看另外一种情况,SQL2

  1. set autocommit=0;
  2. savepoint s0;
  3. insert into tb1 values(0);
  4. savepoint s1;
  5. insert into tb1 values(1);
  6. savepoint s2;
  7. insert into tb1 values(2);
  8. ...
  9. commit;

这种SQL TokuDB的处理方式是:

  1. a) savepoint的时候:
  2. s1s0的子事务,s2s1的子事务,就这样嵌套下去直到事务栈溢出...
  3. b) commit的时候:
  4. 会先提交s2,然后提交s1,然后提交s0,最后整个事务提交

问题解决

针对开始时的问题,TokuDB内核君进行了大胆猜测:

  1. 在主库执行的是SQL1,一切正常;
  2. 在备库执行的是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盘,有兴趣的同学可以去围观下。