TokuDB · 引擎特性 · REPLACE 语句优化

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云原生数据库 PolarDB 分布式版,标准版 2核8GB
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 背景MySQL 在标准 SQL 外,会扩展一些好用的语法,本文关注的 REPLACE 和 INSERT IGNORE 就属于这类。这 2 个语法都是对 INSERT 的扩展,语义是向表中插入数据,不同之处在于遇到 PK 或者 UK 冲突时的处理:INSERT:报 duplicate key 的错误,数据不插入; REPLACE:删除掉老冲突记录,插入新的记录; INSERT IG

背景

MySQL 在标准 SQL 外,会扩展一些好用的语法,本文关注的 REPLACE 和 INSERT IGNORE 就属于这类。这 2 个语法都是对 INSERT 的扩展,语义是向表中插入数据,不同之处在于遇到 PK 或者 UK 冲突时的处理:

  1. INSERT:报 duplicate key 的错误,数据不插入;
  2. REPLACE:删除掉老冲突记录,插入新的记录;
  3. INSERT IGNORE:不插入数据,但是不报错。
    可以看到,REPLACE 的语义是用新数据取代老数据,INSERT IGNORE 的语义是保留老的数据。

本文将向大家介绍 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 的,可以参考 write_record() 函数)。

对于 INSERT IGNORE:
1. 先尝试做 insert,因为 TokuDB 插入是异步的,为了保证唯一性约束,会先做一次 search,看是否有已经有记录;
2. 如果老记录不存在,就直接插入;如果存在,就向 server 层报 dup key 错误;
3. server 层拿到 dup key 错误后,发现设置了 ignore,就正常返回。

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

做优化的调用栈如下:

#0  do_ignore_flag_optimization()
#1  ha_tokudb::set_main_dict_put_flags()
#2  ha_tokudb::insert_row_to_main_dictionary()
#3  ha_tokudb::write_row()
#4  handler::ha_write_row()
#5  write_record()
#6  mysql_insert()
#7  mysql_execute_command()

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

do_ignore_flag_optimization() 判断能否做这个优化:

static inline bool do_ignore_flag_optimization(
    THD* thd,
    TABLE* table,
    bool opt_eligible) {

    bool do_opt = false;
    if (opt_eligible &&
        (is_replace_into(thd) || is_insert_ignore(thd)) &&
        tokudb::sysvars::pk_insert_mode(thd) == 1 &&
        !table->triggers &&
        !(mysql_bin_log.is_open() &&
         thd->variables.binlog_format != BINLOG_FORMAT_STMT)) {
        do_opt = true;
    }
    return do_opt;
}

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

    else if (using_ignore_flag_opt && is_replace_into(thd)
            && !in_hot_index)
    {
        *put_flags = old_prelock_flags;
    }
    else if (opt_eligible && using_ignore_flag_opt && is_insert_ignore(thd)
            && !in_hot_index)
    {
        *put_flags = DB_NOOVERWRITE_NO_ERROR | old_prelock_flags;
    }
    else
    {
        *put_flags = DB_NOOVERWRITE | old_prelock_flags;
    }

db_put() 中会根据前面设置的 put_flag,决定是调用 toku_ft_insert_unique(),还是toku_ft_maybe_insert(),前者会先调用 toku_ft_lookup() 做唯一性检查,然后再做插入;后者则直接插入。在最终调用 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. REPLACE

    模式 TPS CPU%
    关闭优化 3438.21 900
    开启优化 6590.31 240
  2. INSERT IGNORE

    模式 TPS CPU%
    关闭优化 6165.36 1000
    开启优化 6702.45 240

可以看到对于 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 错误。

  3. 表上不能有 triger
    这个主要是因为优化后语义被改变了,replace 在冲突时没有 delete 操作,insert ignore 引擎层永远是返回成功的。

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
MySQL 关系型数据库 内存技术
MySQL · 新特性分析 · CTE执行过程与实现原理
众所周知,Common table expression(CTE)是在大多数的关系型数据库里都存在的特性,包括ORACLE, SQLSERVER,POSTGRESQL等,唯独开源数据库老大MySQL缺失。CTE作为一个方便用户使用的功能,原本是可以利用普通的SQL语句替代的,但是对于复杂的CTE来说,要模拟出CTE的效果还是需要很大的功夫。如果考虑性能那就更是难上加难了。2013年Guilhem
4070 0
|
关系型数据库 MySQL 索引
MySQL8.0.12 · 引擎特性 · LOB Partial Update优化
在之前,笔者介绍过InnoDB对于lob列的更新优化,即允许对lob类型的列数据进行部分更新。由于undo log page本身的限制(例如无法存储过长的数据),对于大列更新,旧版本被留在数据文件中,在MVCC读时,直接从中读旧版本即可。
2431 0
|
存储 SQL 关系型数据库
MySQL · 引擎特性 · DROP TABLE之binlog解析
Drop Table的特殊之处 Drop Table乍一看,与其它DDL 也没什么区别,但当你深入去研究它的时候,发现还是有很多不同。最明显的地方就是DropTable后面可以紧跟多个表,并且可以是不同类型的表,这些表还不需要显式指明其类型,比如是普通表还是临时表,是支持事务的存储引擎的表还是不支持事务的存储引擎的表等。这些特殊之处对于代码实现有什么影响呢?对于普通表,无论是创建还是删除,数据库
2169 0
|
AliSQL 关系型数据库 索引
AliSQL · 特性介绍 · 动态加字段
背景 加字段作为业务需求变更中最常见的需求,InnoDB引擎表的加字段功能一直以来被运维人员所诟病, 虽然支持了online方式,但随着表空间越来越大,copy整张表的代价也越来越大。 AliSQL版本在InnoDB的compact记录格式的基础上,设计了新的记录格式comfort,支持动态加字段。 使用方法 使用的实例如下: CREATE TABLE test( id int primar
2328 0
HybridDB · 性能优化 · Count Distinct的几种实现方式
前言 最近遇到几个客户在HybridDB上做性能测试时,都遇到Count Distinct的性能调优问题。这里我们总结一下HybridDB中,对Count Distinct的几种处理方式。 我们以一个客户的案例来做说明。客户的典型的业务场景是,在用户行为日志中统计对应类别的行为数,类别有几千个,独立的行为的总量很多,有几千万;为分析行为,要查询一段时间内的基于类别的独立行为数,查询如下(tes
1813 0
|
SQL 缓存 关系型数据库
MySQL · 特性分析 · 执行计划缓存设计与实现
Plan Cache背景知识 一条SQL语句输入到MySQL服务器后,一般要经历:词法语法解析(parse),优化(optimize),生成执行计划(plan)和执行(execute)的过程。词法语法分析,优化以及生成执行计划,这三个阶段的主要输出是SQL语句的执行计划(plan),当SQL语句存在多种执行计划的时候,优化器会从这许多的执行计划中挑选出一个它认为最优的(通常是占用系统资源最少的,
2123 0
|
存储 缓存 MySQL
TokuDB · 源码分析 · 一条query语句的执行过程
Mysql是基于代价cost来选择索引,如果一个表有好几个索引,optimizer会分别计算每个索引访问的代价,选择代价最小的索引进行访问,这个索引也被称为access path。 Pickup index Mysql在执行query语句的时候会在server层计算每个可选索引的代价,并选择代价最小的索引作为访问路径(access path)去引擎读取数据。 server层的handler类为
1562 0