AliSQL · 特性介绍 · 动态加字段

背景

加字段作为业务需求变更中最常见的需求,InnoDB引擎表的加字段功能一直以来被运维人员所诟病,
虽然支持了online方式,但随着表空间越来越大,copy整张表的代价也越来越大。
AliSQL版本在InnoDB的compact记录格式的基础上,设计了新的记录格式comfort,支持动态加字段。

使用方法

使用的实例如下:

CREATE TABLE test(
id int primary key,
name varchar(100),
key(name)
)ENGINE=InnoDB  ROW_FORMAT=comfort;

ALTER TABLE test ADD col1 INT;

这里没有增加新的语法,只是增加了新的InnoDB的记录格式,alter语句保持一致。
可以通过SHOW CREATE TABLE或者查询information_schema.tables查看ROW_FORMAT。

mysql> show create table test\G;
*************************** 1. row ***************************
       Table: test
Create Table: CREATE TABLE `test` (
  `id` int(11) NOT NULL,
  `name` varchar(100) DEFAULT NULL,
  `col1` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=COMFORT
1 row in set (0.00 sec)

实现方法

AliSQL设计了一种新的记录格式,命名为comfort,其格式从compact演化而来:

Compact行记录的格式:

  • 变长字段长度列表:如果列的长度小于255字节,用1字节表示;如果大于255个字节,用2字节表示。
  • NULL标志位:表明该行数据是否有NULL值。占一个字节。
  • 记录头信息:固定占用5字节,每位的含义见下表:
名称 大小(bit) 描述
() 1 未知
() 1 未知
delete_flag 1 该行是否已被删除
min_rec_flag 1 为1,如果该记录是预先被定义为最小的记录
n_owned 4 该记录拥有的记录数
heap_no 13 索引堆中该记录的排序记录
record_type 3 记录类型,000表示普通,001表示B+树节点指针,010表示infimum,011表示supermum,1xx表示保留
next_record 16 页中下一条记录的相对位置

新的Comfort记录格式如下:

[Lens | N_nulls | N_fields | Extra_bytes | columns...]

其中:
1. Extra_bytes中info_bits占用一个bit来标识comfort记录,即记录头中未使用的2个bit中的其中一个。
2. 新增N_fields占用1或者2个Bytes来标识当前记录的column数量:
当记录数小于128个时,占用1个Bytes
当大于等于128时,使用2个Bytes。

实现逻辑

假设变更的case如下:

CREATE TABLE `test` (
  `id` int(11) NOT NULL,
  `name` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=COMFORT;

alter table test add col1 int;

1. alter变更

1. 变更数据字典SYS_TABLES中的n_cols字段,即更新column数量
InnoDB的变更语句如下:

     trx->op_info = "Updating column in SYS_TABLES";
     /* N_COLS include compact format bit.*/
     error = que_eval_sql(
             info,
             "PROCEDURE UPDATE_SYS_TABLES_PROC () IS\n"
             "BEGIN\n"
             "UPDATE SYS_TABLES SET N_COLS=N_COLS+1\n"
             "WHERE ID=:table_id;\n"
             "END;\n",
             FALSE, trx);

2.变更数据字典SYS_COLUMNS,新增一条记录,即新增的column
InnoDB的变更语句如下:

       trx->op_info = "inserting column in SYS_COLUMNS";
       error = que_eval_sql(
               info,
               "PROCEDURE INSERT_SYS_COLUMNS_PROC () IS\n"
               "BEGIN\n"
               "INSERT INTO SYS_COLUMNS VALUES\n"
               "(:table_id, :pos, :name, :mtype, :prtype, :len, :prec);\n"
               "END;\n",
               FALSE, trx);

3. 变更dictionary cache中的dict_table_t对象
新的column需要追加到dict_table_t定义的column数组中,

变更前:
table->columns:
(id, name, row_id, trx_id, undo_ptr)

变更后:
table->columns:
(id, name, col1, row_id, trx_id, undo_ptr)

其代码如下:

      /* The new column will be added into after user_def cols,
      before SYS_COLS(ROW_ID, TRX_ID, ROLL_PTR) in dict_table_t */
      for (ulint i= 0; i < n_cols; i++) {
              col = (dict_col_t*)save_cols + i;
              if (i == n_cols - DATA_N_SYS_COLS) {
                      dict_mem_table_add_col(user_table, user_table->heap,
                                      field->field_name,
                                      mtype, prtype, len);
              }
              dict_mem_table_add_col(user_table, user_table->heap,
                                      col_name,
                                      col->mtype, col->prtype, col->len);
              new_col = dict_table_get_nth_col(user_table, user_table->n_def - 1);
              dict_col_copy_ord_prefix(new_col, col);
      }

4. 变更Dictionary Cache中的dict_index_t对象(Cluster index)

变更前:
Primary key的field数组如下:
(id, trx_id, undo_ptr, name)

变更后:
Primary key的field数组如下:
(id, trx_id, undo_ptr, name, col1)

其代码如下:

       /*The new column will added into after last field in dict_index_t */
       for (ulint i = 0; i < n_fields; i++) {
               dfield = (dict_field_t*)(save_fields) + i;
               if (dfield->col->ind < n_cols - DATA_N_SYS_COLS) {
                       col = dict_table_get_nth_col(user_table, dfield->col->ind);
               } else {
                       col = dict_table_get_nth_col(user_table, dfield->col->ind + 1);
               }
               dict_index_add_col(clust_index, user_table, col, dfield->prefix_len);
       }
       col = dict_table_get_nth_col(user_table, n_cols - DATA_N_SYS_COLS);

5. 变更Dictionary Cache中的dict_index_t对象(Secondary index)

变更前:
secondary index的field数组:(name, id)

变更后:
secondary index的field数组:(name, id)

在变更前后,二级索引所对应的fields没有发生变化,fields所对应的column的位置也没有变更,只是因为dict_table_t对象的columns对象重建了,所以需要变更一下field做引用的culumn,这里需要reload一下即可。

对比Online和Dynamic方式

InnoDB原生的Online方式的步骤大致是:
1. 持有exclusive MDL lock,
2. 根据变更后的表结构新建临时表,
3. 新建log表,记录原表的变更
4. MDL降级为shared 锁,原表允许DML,
5. copy数据到新的临时表,并持续copy log表中的记录
6. MDL升级为exclusive
7. apply完log表中所有的记录,并rename表
8. 删除老表,完成变更

InnoDB新的Dynamic方式的步骤大致是:
1. 持有exclusive MDL lock,
2. 降级为shared的锁,允许DML
3. 升级为exclusive锁
4. 变更数据字典(SYS_TABLES, SYS_COLUMNS)
5. 变更数据字典缓存(dict_table_t, dict_index_t)
6. 释放MDL锁

测试情况:

Compact格式的表加字段,共计20W多条记录的情况下,耗时25.98s。

Comfort格式的表加字段,共计20W多条记录的情况下,耗时0.01s。

总结

动态加字段能够在不copy记录的情况下,秒级完成结构的变更,大大方便了运维DBA人员的日常变更,这个功能patch已经开源在AliSQL版本。
如果有兴趣,可以关注AliSQL的开源项目:https://github.com/alibaba/AliSQL

时间: 2024-12-30 03:00:27

AliSQL · 特性介绍 · 动态加字段的相关文章

青年节献礼:AliSQL青年节版本Release 增动态加字段和Thread Pool

动态加字段和Thread Pool Abstract 加字段作为业务需求变更中最常见的需求,InnoDB引擎表的加字段功能一直以来被运维人员所诟病,虽然支持了online方式,但随着表空间越来越大,copy整张表的代价也越来越大.AliSQL版本在InnoDB的compact记录格式的基础上,设计了新的记录格式comfort,支持动态加字段. MySQL默认的one-thread-per-connection的线程模型,在面对大并发的连接请求的时候,变成了性能杀手,随着线程的增多,吞吐能力会急剧

AliSQL · 特性介绍 · 支持 Invisible Indexes

前言 MySQL 8.0 引入了 Invisible Indexes 这一个特性,对于 DBA 同学来说是一大福音,索引生命周期管理除了有和无外,又多了一种形态–可见和不可见,进而对业务SQL的调优又多了一种手段. 关于 Invisible Indexes,不管是官方还是第三方,都有非常多的介绍文档,这里推荐大家可以先看下: 官方文档: Invisible Indexes 官方 server 层团队博客: MySQL 8.0: Invisible Indexes 官方 worklog: WL#8

javascript动态加载多种方法详细介绍(1/4)

这两天,没什么太多的事情,好吧,我承认,是我这两天不想做公司的项目,因为我突发奇想,其实也不算突发奇想,算是对以前的想法的实现,就是把JS当做Java来写. 现在也有很多JS动态加载的框架,比如In.js.但是这种并不是我想要的编写方式,我来说说我的想法. 先来一段java代码  代码如下 复制代码 import Biz.User; User u = new User(); u.show(); 按流程就是导包.实例化.调用. JS是做不了导包的,或者说代码意义上的导包,一般只是在页面上进行scr

携程Android App的插件化和动态加载框架

携程Android App的插件化和动态加载框架已上线半年,经历了初期的探索和持续的打磨优化,新框架和工程配置经受住了生产实践的考验.本文将详细介绍Android平台插件式开发和动态加载技术的原理和实现细节,回顾携程Android App的架构演化过程,期望我们的经验能帮助到更多的Android工程师. 需求驱动 2014年,随着业务发展需要和携程无线部门的拆分,各业务产品模块归属到各业务BU,原有携程无线App开发团队被分为基础框架.酒店.机票.火车票等多个开发团队,从此携程App的开发和发布

DataTables+BootStrap组合使用Ajax来获取数据并且动态加载dom的方法(排序,过滤,分页等)_jquery

Datatables是一款jquery表格插件.它是一个高度灵活的工具,可以将任何HTML表格添加高级的交互功能. 主要功能 分页,即时搜索和排序 几乎支持任何数据源:DOM, javascript, Ajax 和 服务器处理 支持不同主题 DataTables, jQuery UI, Bootstrap, Foundation 各式各样的扩展: Editor, TableTools, FixedColumns -- 丰富多样的option和强大的API 支持国际化 超过2900+个单元测试 免

Visual Basic .NET 中动态加载类 (二)

visual|动态|加载 Microsoft 步骤 3:创建配置文件以存放可用窗体 应用程序在运行时需要的某些信息可能在编译时无法提供,这些信息通常放置在配置文件中.在 Visual Basic 6.0 中,配置文件应该是 INI 文件或 Windows 注册表.而在 .NET 中,则使用基于 XML 的配置文件. 我们无法详细介绍配置文件,因为这个主题非常复杂.但是,您应该知道,Windows 窗体应用程序的配置文件与应用程序的 EXE 启动文件在同一个目录中.配置文件的名称与程序的 EXE

gridpanel动态加载数据的实例代码

这篇文章介绍了gridpanel动态加载数据的实例代码,有需要的朋友可以参考一下   复制代码 代码如下: <script type='text/javascript'> function editEsOrder(options,success,response){ if (success){ var tagModelManager_maxOrderIdJson=Ext.util.JSON.decode(response.responseText); var orderValue = tagM

动态加载dtree.js树treeview

 本篇文章主要是对动态加载dtree.js树treeview的示例代码进行了详细的分析介绍,需要的朋友可以过来参考下,希望对大家有所帮助 代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html> <head> <title>Destr

《Java数字图像处理:编程技巧与应用实践》——1.4 Swing Java 2D的其他高级特性介绍

1.4 Swing Java 2D的其他高级特性介绍 1 . Stroke接口 Stroke是Graphics2D的API接口,用来实现图形的描边修饰,在Java 2D中只有一个完成Stroke接口的类BasicStroke,如果有需要,可以自己完成Stroke接口,实现自定义的Stroke类.如何使用Stroke的实现类?方法如下: 1)调用Graphics2D 的setStroke()方法,传入一个实例化的Stroke对象. 2)调用draw()方法,传入要绘制的几何形状. BasicStr