1. INSERT:报 duplicate key 的错误,数据不插入;
  2. REPLACE:删除掉老冲突记录,插入新的记录;

本文将向大家介绍 TokuDB 引擎对这个2个语法的优化。

我们先看下优化前,一次 REPLACE 和 INSERT IGNORE 都需要做哪些操作。

对于 REPLACE:

  1. 先尝试做 insert,因为 TokuDB 插入是异步的,为了保证唯一性约束,会先做一次 search,看是否有已经有记录;
  2. 如果老记录不存在,就直接插入;如果存在,就向 server 层报 dup key 错误;
  3. server 层拿到 dup key 错误后,再向引擎发一次 search 操作,把老记录捞出来;
  4. server 层有了老记录和要插入的数据,调引擎层的 update 接口做更新(其实这里应该做 delete + insert,server 层做了一个优先,只需要调一次引擎接口;如果有外键或者有delete触发器的话,还是会做 delete + insert 的,可以参考 函数)。

对于 INSERT IGNORE:

  1. 先尝试做 insert,因为 TokuDB 插入是异步的,为了保证唯一性约束,会先做一次 search,看是否有已经有记录;
  2. server 层拿到 dup key 错误后,发现设置了 ignore,就正常返回。

TokuDB 优化后是怎样的呢,REPLACE 和 INSERT IGNORE 只需要做一次插入即可,TokuDB 对写是做了优化的,只需要将 msg 放在 FT 的 root 节点即可,后台线程会异步将其应用到 leaf 节点(参考TokuDB索引结构–Fractal Tree),所以性能提升是比较明显的。

主要代码逻辑在 ha_tokudb::set_main_dict_put_flags()do_ignore_flag_optimization() 这2个函数中。

判断能否做这个优化:

ha_tokudb::set_main_dict_put_flags() 根据 do_ignore_flag_optimization() 返回的结果和当前语句设置 put_flag。

中会根据前面设置的 put_flag,决定是调用 toku_ft_insert_unique(),还是toku_ft_maybe_insert(),前者会先调用 做唯一性检查,然后再做插入;后者则直接插入。在最终调用 toku_ft_root_put_msg(),将 msg 放在root节点时,会根据之前的flag 生成不同 type 的msg,如 INSERT IGNORE 的 type 就设置为 FT_INSERT_NO_OVERWRITE,表示msg类型是插入,但是如果有老记录时不覆盖,后台 apply 线程在应用时,就会根据 msg 的类型采取相应的处理。

为了能够方便的开启和关闭这个优化,笔者在代码里加了一个开关。测试是用 sysbench,开32个线程,一个事务里就一条语句(REPLACE 或者 INSERT IGNORE),表结构就是sysbench默认的,但是去掉了二级索引,另外 binlog 是关闭的,结果如下:

  1. INSERT IGNORE

    模式TPSCPU%
    关闭优化6165.361000
    开启优化6702.45240

可以看到对于 REPLACE 的优化效果非常明显,用更低的 CPU 消耗获得了更高的 TPS;对于 INSERT IGNORE,CPU 消耗大大降低,TPS 有一定提升。 INSERT IGNORE 优化效果没有 REPLACE 这么明显,是因为 INSERT IGNORE 本身的逻辑要比 REPLACE 简单,在优化前如果冲突记录存在的话,是直接返回的。

需要注意的是,这个优化并不是通用的,具体的限制如下:

  1. 只能有一个PK,不能其它任务用二级索引 PK所在的FT做插入时,是直接把 msg 放到 root 节点的,根本就没有取可能存在的老记录,所以二级索引的更新是没法做的。

  2. 要求 binlog 用 statement 格式,或者关闭 binlog 如果 binlog 是 row 格式的话,会导致备库应用报错,所有的操作都记为 Write_rows event,如果有记录冲突的话,备库执行时直接报 dup key 错误。