0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

MySQL 8.0 29 instant add/drop column性能回退问题

数据库和存储 来源:MySQL内核剖析 2023-12-11 11:20 次阅读

影响范围: 从 8.0.29 版本开始, 在read heavy 场景, 性能可能有 5%~10% 的性能回退

MySQL 官方在8.0.29 里面加了instant add/drop column 能力, 能够实现 instant add 或者 drop cloumn 到表的任意位置. PolarDB 在这基础上增加了可以 Instant 修改列的能力, 具体可以看我们的月报

instant DDL 核心观点只有一个: don’t touch any row but update the metadata only, 也就是仅仅去修改 Data Dictionary(DD) 信息, 而不去修改数据信息,这样才有可能做到 Instant.

具体的做法就是给每一个行增加了row_version, 然后DD 本身就是多版本, 不同的数据信息用不同的DD 信息去解析.

首先一个record 是否有row_version 信息添加到了Record info bits 里面.

info bits 包含有deleted flag, min record 等等信息, 后来在8.0.13 的时候增加record 是否有Instant ADD column 信息. 在 8.0.29 版本中增加了record 是否有 row_version 信息.

8976f392-97c9-11ee-8b88-92fbcf53809c.png

以上是这个 issue 背景, Instant add/drop column 的原理, 但是原因在哪里呢?

从Markus 提交上来的Flamegraph 可以看到, 在 8.0.33 里面 rec_get_offsets/cmp_dtuple_rec/rec_get_nth_field 等等相比 8.0.28 占比明显增多了. 整个 row_serch_mvcc 的调用开销也增加了.

899071f0-97c9-11ee-8b88-92fbcf53809c.png

899abade-97c9-11ee-8b88-92fbcf53809c.png

核心原因由于数据record 增加了 row_version 信息, 导致在执行数据解析的函数 rec_get_offsets/rec_get_nth_field 等函数中增加了很多额外的判断, 并且官方把很多 inline function 改成了 non-inline.

为了验证想法, 我们做了 3 个地方的修改, 具体可以看 Issue 上面的代码提交:

1. 将一些 non-inline function 改回inline function

从 inline => non-inline. 修改的函数如下:

8.0.27

rec_get_nth_field => inline

rec_get_nth_field_offs => inline

rec_init_offsets_comp_ordinary => inline

rec_offs_nth_extern => inline

8.2.0

rec_get_nth_field => non-inline

rec_get_nth_field_offs => non-inline

rec_init_offsets_comp_ordinary => non-inline

rec_offs_nth_extern => non-inline

我们测试下来在 oltp_read_only 场景里面, 将这些 non-inline 函数改成 inline 以后, 性能可以有 3~5% 左右的提升空间. 具体改动代码可以在 issue 里面获得.

2. 简化get_rec_insert_state 逻辑

8.0.29 增加了 get_rec_insert_state 函数, 需要判断当前 record 是来自哪一个版本升级上来的, 从而使用合适的 DD 代码逻辑进行解析. 如果是包含有 row_version 版本, 还需要判断是否带有 version 信息, 如果没有 version 信息, 是不是8.0.12 instant add column 版本等等, 这里的逻辑非常琐碎.

所以 REC_INSERT_STATE 的状态非常多.

enum REC_INSERT_STATE {  /* Record was inserted before first instant add done in the earlier
  implementation. */
  INSERTED_BEFORE_INSTANT_ADD_OLD_IMPLEMENTATION,  /* Record was inserted after first instant add done in the earlier
  implementation. */
  INSERTED_AFTER_INSTANT_ADD_OLD_IMPLEMENTATION,  /* Record was inserted after upgrade but before first instant add done in the
  new implementation. */
  INSERTED_AFTER_UPGRADE_BEFORE_INSTANT_ADD_NEW_IMPLEMENTATION,  /* Record was inserted before first instant add/drop done in the new
  implementation. */
  INSERTED_BEFORE_INSTANT_ADD_NEW_IMPLEMENTATION,  /* Record was inserted after first instant add/drop done in the new
  implementation. */
  INSERTED_AFTER_INSTANT_ADD_NEW_IMPLEMENTATION,  /* Record belongs to table with no verison no instant */
  // 如果index 上面没有做过instant add 或者 最新的row_version 版本Instant add/drop
  INSERTED_INTO_TABLE_WITH_NO_INSTANT_NO_VERSION,
  NONE
};

具体获得 insert_state 代码:

static inline enum REC_INSERT_STATE get_rec_insert_state(    const dict_index_t *index, const rec_t *rec, bool temp) {
  ut_ad(dict_table_is_comp(index->table) || temp);  if (!index->has_instant_cols_or_row_versions()) {    return INSERTED_INTO_TABLE_WITH_NO_INSTANT_NO_VERSION;
  }  /* Position just before info-bits where version will be there if any */
  const byte *v_ptr =
      (byte *)rec -
      ((temp ? REC_N_TMP_EXTRA_BYTES : REC_N_NEW_EXTRA_BYTES) + 1);  const bool is_versioned =
      (temp) ? rec_new_temp_is_versioned(rec) : rec_new_is_versioned(rec);  // 如果有versioned 以后, 这里可以看到version 值是保存在Info bits 和 null field bitmap 中间的1 byte, 如下图
  const uint8_t version = (is_versioned) ? (uint8_t)(*v_ptr) : UINT8_UNDEFINED;  const bool is_instant = (temp) ? rec_get_instant_flag_new_temp(rec)
                                 : rec_get_instant_flag_new(rec);  // 说明一个Record 不能同时被instalt add 和 row_version 版本instant add/drop 处理过
  // 应该以后默认的新版本是row_version 版本 instant add/drop, 老的要被淘汰
  if (is_versioned && is_instant) {
    ib::error() << "Record has both instant and version bit set in Table '"
                << index->table_name << "', Index '" << index->name()
                << "'. This indicates that the table may be corrupt. Please "
                   "run CHECK TABLE before proceeding.";
  }  enum REC_INSERT_STATE rec_insert_state = REC_INSERT_STATE::NONE;  if (is_versioned) {
    ut_a(is_valid_row_version(version));    if (version == 0) {
      ut_ad(index->has_instant_cols());      // is_versioned 说明record 有row_version, 如果version = 0, 说明是row_version DD 之前插入, 然后row_version DD 做过以后, 又升级了实例, 所以给这些row_version 设置成0
      rec_insert_state =
          INSERTED_AFTER_UPGRADE_BEFORE_INSTANT_ADD_NEW_IMPLEMENTATION;
    } else {      // 最正常的record, row_version DD 之后插入的, 有自己的row_version 版本
      ut_ad(index->has_row_versions());
      rec_insert_state = INSERTED_AFTER_INSTANT_ADD_NEW_IMPLEMENTATION;
    }
  } else if (is_instant) {    // 到这里说明record 上面没有row_version DD 标记, 只有instant add 标记
    // 说明这个Record 是Instant add 之后插入的record, 并且没有做过row_version DD
    ut_ad(index->table->has_instant_cols());
    rec_insert_state = INSERTED_AFTER_INSTANT_ADD_OLD_IMPLEMENTATION;
  } else if (index->table->has_instant_cols()) {    // 到这里说明record 上面 没有row_version DD 和 instant add 标记, 但是这个index 上面有instant add 标记
    // 说明这个record 是instant add 之前就插入的
    rec_insert_state = INSERTED_BEFORE_INSTANT_ADD_OLD_IMPLEMENTATION;
  } else {    // record 上面没有row_version DD, 也没用instant add 标记, 并且index 上面也没用instant add
    // 那么这个Record 是在row_version DD 以及 instant add 做过之前就插入的
    rec_insert_state = INSERTED_BEFORE_INSTANT_ADD_NEW_IMPLEMENTATION;
  }

  ut_ad(rec_insert_state != REC_INSERT_STATE::NONE);  return rec_insert_state;
}

这里虽然 inline enum REC_INSERT_STATE get_rec_insert_state 定义的是 inline, 但是其实这个只是代码给编译器的定义, 具体函数是否 Inline 其实是编译器自己决定的, 最后其实具体运行的时候该函数并没有 inline, 因为可以从Flamegraph 看到, 说明这个函数是有符号表的信息的, 因此肯定不是 inline 的

89bacf18-97c9-11ee-8b88-92fbcf53809c.png

3. 将 swatch case 改成 if/else, 并且给编译器提示likely 执行的 branch

最后我们发现 switch case 对于有些明显的分支预测并不友好, 通过 if/else 可以手动调整哪些 branch 更有可能执行, 从而优化编译器的选择.

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 编译器
    +关注

    关注

    1

    文章

    1602

    浏览量

    48894
  • MySQL
    +关注

    关注

    1

    文章

    789

    浏览量

    26283

原文标题:#issue 111538 MySQL 8.0 instant add/drop column 性能回退问题

文章出处:【微信号:inf_storage,微信公众号:数据库和存储】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    MySQL8.0 新特性:Partial Update of LOB Column

    是标记删除旧记录,并插入新记录,显然这会带来一些存储上的开销(尽管Purge线程会去后台清理),而写入的redo log和Binlog的量也会偏高,对于超大列,可能会严重影响到性能MySQL8.0
    发表于 06-11 20:23

    ADD8504/ADD8505/ADD8506,pdf da

    The ADD8504, ADD8505, and ADD8506 are 4-, 5-, and 6-channel LCD gamma reference buffers designed
    发表于 09-02 16:16 15次下载

    即时效能与闪存微控制器-Instant Performanc

    flash and SRAM. These UHSFMs drop into existing 8051 applications and deliver an instant boost in speed. This application note discusse
    发表于 04-23 13:57 505次阅读

    什么是ADM (Add/Drop Multiplexer)

    什么是ADM (Add/Drop Multiplexer)  英文缩写: ADM (Add/Drop Multiplexer) 中文译名: 分插复用器 分  类:
    发表于 02-22 10:10 2573次阅读

    MySql5.6性能优化最佳实践

    MySql5.6性能优化最佳实践
    发表于 09-08 08:47 13次下载
    <b class='flag-5'>MySql</b>5.6<b class='flag-5'>性能</b>优化最佳实践

    Instant Raspberry Pi Gaming

    Instant Raspberry Pi Gaming
    发表于 10-24 09:26 6次下载
    <b class='flag-5'>Instant</b> Raspberry Pi Gaming

    腾讯云打造MySQL 8.0全新引擎,进一步加速客户产业升级

    据介绍,腾讯云数据库 MySQL 8.0的内核可以百分百完全兼容主流MySQL分支。相比官方版本,无论是单机模式、异步模式还是同步模式下, MySQL
    的头像 发表于 07-09 14:54 2270次阅读

    MySQL 5.7与MySQL 8.0 性能对比

    背景 测试mysql5.7和mysql8.0分别在读写,选定,只写模式下不同并发时的性能(tps,qps) 最早 测试使用版本为mysql5.7.22和
    的头像 发表于 11-03 09:26 1.6w次阅读
    <b class='flag-5'>MySQL</b> 5.7与<b class='flag-5'>MySQL</b> <b class='flag-5'>8.0</b> <b class='flag-5'>性能</b>对比

    MySql环境一键安装应用程序免费下载

    本文档的主要内容详细介绍的是MySql环境一键安装应用程序免费下载。创建Mysql所需环境支持8.0以上版本,暂无测试过8.0以下版本
    发表于 02-26 15:01 7次下载

    关于MySQL8.0版本选型的小技巧

    MySQL 8.0 第一个GA(General Availability)版本(正式、可用于生产的版本)于2018/4/19发布至今已有3年。8.0是一个全新的版本,增加了数百项功能新特性,重构了
    的头像 发表于 03-29 13:45 1030次阅读
    关于<b class='flag-5'>MySQL8.0</b>版本选型的小技巧

    请问mysql8.0不能在grant时创建用户是什么原因?

    用习惯了MySQL5.7,当在MySQL8.0里创建用户时,习惯性直接敲GRANT指令,结果报错了
    的头像 发表于 08-11 10:16 1908次阅读

    mysql8.0默认字符集是什么

    MySQL 8.0 默认字符集是 utf8mb4。 MySQL 8.0 是当前最新的开源关系型数据库管理系统,由Oracle公司开发和维护。MySQ
    的头像 发表于 11-16 14:48 1581次阅读

    mysql数据库的增删改查sql语句

    MySQL是一种常用的关系型数据库管理系统,是许多网站和应用程序的首选数据库。在MySQL中,我们可以使用SQL(结构化查询语言)进行数据的增删改查操作。本文将详细介绍MySQL数据库的增删改查
    的头像 发表于 11-16 15:41 1014次阅读

    MySQL5.7数据导入8.0版本,这3款工具值得收藏!

    MySQL 5.7数据库迁移到MySQL 8.0可以使用NineData、MySQL Shell、Percona XtraBackup和Liquibase等工具。每个工具都有自己的优
    的头像 发表于 11-29 16:47 2040次阅读
    <b class='flag-5'>MySQL</b>5.7数据导入<b class='flag-5'>8.0</b>版本,这3款工具值得收藏!

    GitHub底层数据库无缝升级到MySQL 8.0的经验

    GitHub 团队近日分享了他们将 GitHub.com 的底层数据库无缝升级到 MySQL 8.0 的经验。 据介绍,GitHub 使用 MySQL 来存储大量关系数据,因此在不影响网站服务级别
    的头像 发表于 12-13 10:21 390次阅读
    GitHub底层数据库无缝升级到<b class='flag-5'>MySQL</b> <b class='flag-5'>8.0</b>的经验