使用方法

使用的实例如下:

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

  1. *************************** 1. row ***************************
  2. Table: test
  3. Create Table: CREATE TABLE `test` (
  4. `id` int(11) NOT NULL,
  5. `name` varchar(100) DEFAULT NULL,
  6. `col1` int(11) DEFAULT NULL,
  7. PRIMARY KEY (`id`),
  8. KEY `name` (`name`)
  9. ) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=COMFORT
  10. 1 row in set (0.00 sec)

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

Compact行记录的格式:

  • 变长字段长度列表:如果列的长度小于255字节,用1字节表示;如果大于255个字节,用2字节表示。
  • NULL标志位:表明该行数据是否有NULL值。占一个字节。
  • 记录头信息:固定占用5字节,每位的含义见下表:

新的Comfort记录格式如下:

其中:

  1. 新增N_fields占用1或者2个Bytes来标识当前记录的column数量: 当记录数小于128个时,占用1个Bytes 当大于等于128时,使用2个Bytes。

实现逻辑

假设变更的case如下:

  1. CREATE TABLE `test` (
  2. `id` int(11) NOT NULL,
  3. PRIMARY KEY (`id`),
  4. KEY `name` (`name`)
  5. ) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=COMFORT
  6. alter table test add col1 int;

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

  1. trx->op_info = "inserting column in SYS_COLUMNS";
  2. error = que_eval_sql(
  3. info,
  4. "PROCEDURE INSERT_SYS_COLUMNS_PROC () IS\n"
  5. "BEGIN\n"
  6. "INSERT INTO SYS_COLUMNS VALUES\n"
  7. "(:table_id, :pos, :name, :mtype, :prtype, :len, :prec);\n"
  8. 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)

其代码如下:

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)

  1. /*The new column will added into after last field in dict_index_t */
  2. for (ulint i = 0; i < n_fields; i++) {
  3. if (dfield->col->ind < n_cols - DATA_N_SYS_COLS) {
  4. col = dict_table_get_nth_col(user_table, dfield->col->ind);
  5. } else {
  6. col = dict_table_get_nth_col(user_table, dfield->col->ind + 1);
  7. }
  8. dict_index_add_col(clust_index, user_table, col, dfield->prefix_len);
  9. }
  10. 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一下即可。

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. 释放MDL锁

测试情况:

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

总结

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