MySQL在线加字段实现原理
博客已转移到腾讯DBA博客
腾讯互娱内部维护了一个MySQL分支,基于官方5.5.24,实现了类似于Oracle 11g的快速加字段功能,这个分支我们内部称为TMySQL。该功能通过扩展存储格式来实现,原理类似于Oracle 11g,以下介绍下其实现原理。
1. GCS行格式
需要在innodb中实现类似oracle的快速加字段功能,仅仅基于原来的行格式是不行的,必须对其进行扩展。为了保证原MySQL和innodb的兼容性,不改变原存储格式的行为,在线加字段功能是通过新增一种innodb行格式GCS(Game Cloud Storage)来实现,从而避免对其他格式造成影响。

虽然说是新增格式,但该格式与原默认的compact是基本一样的,只是新增了关键的Field Count(字段数)控制信息。
下面先回顾下compact行格式
compact聚集索引记录的格式由下图所示,由记录头和记录内容组成。

记录头记录了解析字段的关键信息,其结构下图所示。

- Record Extra Bytes:是固定的5字节,表示记录的额外信息。
 - Nullable Bitmap:记录可空位图,使用1个bit来表示一个可空字段是否为NULL。
 - non-NULL Variable-Length Array:非空变长字段数组,表示非空变长字段(如varchar、varbinary)在该行的长度。
 
关于Compact格式的详细介绍可以参考文章。
compact格式有很重要的两个特点:
- 字段的定义顺序与存储顺序基本一致(主键除外)
 - 字段为NULL不占存储空间,只需通过Nullable Bitmap记录。
 
基于以上特点,实现快速加字段功能GCS格式只需在记录头添加Field Count(字段数),来表示当前记录的字段数,占用1~2字节(若大于127,则使用2字节)。

那么,解析GCS记录的关键逻辑就变为:
假设某GCS记录的Field count记录为x,表字段数为y,那么x <= y总是成立的。对于GCS格式,字段的解析变为:
- 如果x == y或非GCS格式,按原来compact方式解析
 - 如果x < y,那么记录必定是在Alter Table加字段前就生成,那么最后y-x个字段必为NULL或默认值。
- 如果该字段是允许为NULL,则为NULL。
 - 如果该字段不允许为NULL,则为默认值。
 
 
这样就可以将新增的字段解析出来。
另外,GCS格式在TMySQL中会替代compact作为innodb的默认行格式。
mysql> create table t1(c1 int, c2 int);
Query OK, 0 rows affected (0.00 sec)
mysql> show table status like 't1'\G
*************************** 1. row ***************************
           Name: t1
         Engine: InnoDB
        Version: 10
     Row_format: Gcs
           Rows: 0
 Avg_row_length: 0
    Data_length: 16384
Max_data_length: 0
   Index_length: 0
      Data_free: 0
 Auto_increment: NULL
    Create_time: 2013-09-10 11:44:21
    Update_time: NULL
     Check_time: NULL
      Collation: utf8_general_ci
       Checksum: NULL
 Create_options:
        Comment:
1 row in set (0.00 sec)
# 插入大量数据
mysql> insert into t1 values(1,1);
Query OK, 1 row affected (0.00 sec)
# 快速加字段
mysql> alter table t1 add column d1 int not null default 0, add column d2 int;
Query OK, 0 rows affected (0.00 sec)
Records:   Duplicates:   Warnings: 0
mysql> select * from t1 limit 1;
+------+------+----+------+
| c1   | c2   | d1 | d2   |
+------+------+----+------+
|1     |1     |  0 | NULL |
+------+------+----+------+
1 row in set (0.00 sec)
2. 兼容性
仅仅新增GCS存储格式,必定导致原来数据的不兼容(每行增加Field Count),只能通过重导数据或alter table来使其支持在线加字段功能。
# compact格式转换为GCS格式
mysql> alter table t_compact row_format=gcs;
如果数据量越大,上述语句所花时间就会越长,这样对升级和使用都带来困难。
为了解决这个问题,GCS格式实现了对compact格式的全面兼容。
在innodb实现中,compact格式记录头的Record Extra Bytes首字节最高位和次高位是保留的,总是为0。因此,GCS记录占用了其中的最高bit,若为1表示使用GCS格式,记录中会包含Field count信息,这里称该bit为控制位。
然而GCS行格式新增的Field Count仅仅是为了表示记录的字段数,如果表不执行加字段操作,该部分其实是不起作用的(记录字段数恒等于表字段数)。换句话说,如果GCS表不执行快速加字段操作,记录其实并不需要存储Field Count信息。
因此,当表第一次快速加字段前,GCS记录可以与compact完全相同,即控制位为0;当第一次快速加字段时,可将加字段前的字段数存储在扩展的数据字典中,用来解析控制位为0的GCS记录(充当记录上的field count信息);新插入或修改的记录,则按照第一节介绍的存储格式进行构建。
最后,这个设计中的GCS记录在第一次快速加字段前可以跟Compact格式是完全一致的。这样做,有两个好处:
- 第一次在线加字段前,减少每条GCS记录Field Count 1~2字节的存储消耗
 - 可以支持Compact表快速转换为GCS表,实现快速升级
 
由于存储格式完全兼容,不管原来的compact表数据量有多少,仅需执行以下命令,修改表的元数据信息,就能将格式快速转换为GCS,从而获得快速加字段功能。
mysql> create table t_compact(c1 int, c2 int) row_format=compact;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t_compact values(1,1);
Query OK, 1 row affected (0.00 sec)
# compact格式非onlie操作,需要拷贝数据
mysql> alter table t_compact add column d1 int;
Query OK, 1 row affected (0.01 sec)
Records: 1  Duplicates: 0  Warnings: 0
# online操作,只需修改元数据,不需拷贝数据
mysql> alter table t_compact row_format = gcs;
Query OK, 0 rows affected (0.00 sec)
Records:   Duplicates:   Warnings: 0
# online操作
mysql> alter table t_compact add column d2 int not null default 0;
Query OK, 0 rows affected (0.00 sec)
Records:   Duplicates:   Warnings: 0
这样,TMySQL可实现基于5.5的覆盖升级,对于compact表只需执行alter table row_format = gcs命令就能快速支持在线加字段功能,大大降低升级成本,并且实现加字段前没有存储空间的额外消耗!
3 DML操作
存储格式得以支持,相应的DML操作逻辑需要修改。
- select:按照GCS格式进行解析
 - insert:根据当前表的字段数构造记录中field count和标记控制位
 - delete:不变
 - update:原地更新不变;非原地更新走delete+insert,会更新为新的field count
 
4 数据字典
InnoDB关于列的数据字典中并不包含默认值信息,默认值只存储在MySQL层的frm文件中。
为了支持默认值的加字段操作,InnoDB存储引擎层需要存储新增列的默认值信息。一种办法是在原SYS_COLUMNS系统表增加def_val列,表示默认值。但这必定导致TMySQL和官方版本的不兼容,并且不是所有列的默认值都需要存储的,只需保存新增列的默认值信息。
因此,TMySQL新增一个系统表SYS_ADDED_COLS_DEFAULT,保存新增列的默认值,并通过(tableid,pos)与SYS_COLUMNS的列信息关联起来。
SYS_ADDED_COLS_DEFAULT(
    tableid     bigint,
    pos         int,
    def_val     binary(65535),
    def_val_len int,
    Primary key (tableid, pos)
);
为了实现与官方版本兼容,SYS_ADDED_COLS_DEFAULT使用动态创建功能,即TMySQL启动加载数据字典子系统时发现该表不存在,会动态创建。这样,从官方版本升级原地升级为TMySQL时就不需考虑该表导致的不兼容问题。
除了默认值,systables系统表还需记录GCS格式标记,第一次加字段前字段数等新增元数据信息。
5 加字段流程
MySQL官方版本加字段其实是新建表导入数据,并删除原表的过程,整个过程是阻塞写的。
而TMySQL的GCS格式支持原数据不改变的情况下解析新增字段,并且可以在原表上直接修改元数据,不需拷贝数据,从而实现加字段时间可以大大缩短。
GCS表在线加字段的过程为:
- 目标表T1上字典锁(SHARED_NO_WRITE),阻塞所有写操作
 - 判断是否符合在线加字段的条件,如符合,执行步骤3,否则按照官方拷贝数据的方式来新增字段。
 - 创建跟目标表T1同构的临时表frm文件S1.frm(含新增字段)
 - 目标表T1的字典锁升级为排它锁,所有关于T1表的所有读写操作被阻塞
 - 修改T1表InnoDB内部数据字典信息,增加新字段。
 - 将S1.frm重命名为T1.frm
 - 释放表T1的字典锁
 
以上流程处理参考了5.6 online DDL的部分实现。
TMySQL并不支持任何情况的加字段操作,必须符合以下条件:
- 表必须是innodb的GCS表,原Compact表不支持在线加字段功能。
 - 不支持临时表。
 - 一次alter table仅允许加一列或多列,但不允许同时进行多个alter table的不同操作(如增删索引、删字段、修改字段等)。
 - 加字段不支持指定Before或After关键字表示定义在某列之前或之后。
 - 所加字段不能包含除not null外的任何约束,包括外键约束、唯一约束。
 - 不支持允许为NULL并指定默认值的加字段操作(同oracle 11g)
 - 所加字段不能自增列(auto_increment)
 
6 总结
以上是TMySQL在线加字段操作的实现原理,除了以上内容还包括语法、故障恢复、事务回滚、自适应哈希索引等适配改造,比较细节,这里就先不介绍了。对以上有任何疑问,欢迎讨论~
除了在线加字段,TMySQL还有不少其他新特性,后续再介绍了。
最后,广告一则:
腾讯互娱DBA团队急需MySQL开发人员,如果您熟悉数据基本原理,如果您熟悉C/C++,如果您想加入我们正在快速成长的团队共同专研数据库技术,欢迎发简历到vinchen@tencent.com。
MySQL在线加字段实现原理的更多相关文章
- mysql 在线加索引 锁表
		
mysql在线修改表结构大数据表的风险与解决办法归纳 - 王滔 - 博客园 http://www.cnblogs.com/wangtao_20/p/3504395.html MySQL 加索引 加字段 ...
 - mysql 在线添加字段
		
使用工具pt-online-schema-change #! /bin/bash stime=`date +%s` echo "增加字段开始测试时间为:`date +%H:%M:%S`&qu ...
 - mysql在线修改表结构大数据表的风险与解决办法归纳
		
整理这篇文章的缘由: 互联网应用会频繁加功能,修改需求.那么表结构也会经常修改,加字段,加索引.在线直接在生产环境的表中修改表结构,对用户使用网站是有影响. 以前我一直为这个问题头痛.当然那个时候不需 ...
 - MySQL5.6在线DDL不锁表(在线添加字段)
		
解答你也看一下MySQL5.6在线DDL不锁表,现在我有一张1亿的表,需要增加一个字段,假如我让你去增加这个字段,你应该注意什么,具体怎么操作? 操作如下:1.注意磁盘空间(临时表目录 参数 tmpd ...
 - 数据库遇到的问题——mysql在线修改表结构大数据表的风险与解决办法归纳
		
互联网应用会频繁加功能,修改需求.那么表结构也会经常修改,加字段,加索引.在线直接在生产环境的表中修改表结构,对用户使用网站是有影响. 以前我一直为这个问题头痛.当然那个时候不需要我来考虑,虽然我们没 ...
 - mysql变成类型字段varchar值更新变长或变短底层文件存储原理
		
为了搞清楚MySQL对于可变长度字段值修改时,如何高效操作数据文件的机制.之前一直模糊不清,网上也搜不到现成的答案.经过多方资料搜集整理.写出此文供大家一起参阅.由于涉及众多非常底层的知识,我假设读者 ...
 - MySQL锁(二)表锁:为什么给小表加字段会导致整个库挂掉?
		
概述 表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持.最常使用的MYISAM与INNODB都支持表级锁定.表级锁定分为表共享 ...
 - MySQL如何安全的给小表加字段
		
MySQL学习笔记-如何安全的给小表加字段 如果要给一个大表加字段,你一般都会非常谨慎小心,以免对线上业务造成影响,但实际上给一个小表加字段不慎操作也会导致线上业务出问题,这篇文章主要学习一下MySQ ...
 - MyISAM表加字段的特殊方法
		
最近一个统计系统的大表需要加字段,表的引擎是myisam,表大小在3亿,物理文件在106G.想想都蛋疼.那么这种情况下怎么把字段撸上去呢? 1. 首先想到了<高性能MySQL>提到的直接更 ...
 
随机推荐
- 开源一个Java Class实现Openfire登陆、推出、消息发送,方便其他系统集成IM功能了
			
开源一个Java Class实现Openfire登陆.推出.消息发送 N年前写的,希望对Openfire开发新手有帮助哦 import java.util.*; import java.io.*; ...
 - Jexus高级功能设置
			
我们对服务器软件Jexus作了简单的介绍,同时我们也对Jexus的整体配置作了详细的讲解,介绍了Jexus的进程守护工具"jws.guard",相信各位读者对于Jexus应该已经有 ...
 - 菜单根据菜单ID向下递归
			
第一步:我们根据这个类定义数据库,并插入菜单数据 DROP TABLE IF EXISTS `jrbac_menu`; CREATE TABLE `jrbac_menu` ( `id` ) NOT N ...
 - java中为什么重写equals时必须重写hashCode方法?
			
在上一篇博文Java中equals和==的区别中介绍了Object类的equals方法,并且也介绍了我们可在重写equals方法,本章我们来说一下为什么重写equals方法的时候也要重写hashCod ...
 - python-Event事件处理进程同步
			
#!/usr/bin/python from multiprocessing import Process,Event import os,time def A(e): print "blo ...
 - java算法----------常用的加密算法
			
散列算法(单向散列,不可逆) MD5(Message Digest Algorithm 5) SHA(Secure Hash Algorithm) 对称加密(加密解密使用同一密钥,速度快) DES ...
 - java.lang.NoClassDefFoundError: org/springframework/ui/jasperreports/JasperReportsUtils原因
			
在springMVC结合jasperReporter中发现的问题 java.lang.NoClassDefFoundError: org/springframework/ui/jasperreport ...
 - strcpy和strncpy
			
在c语言中,对于简单变量,如int型.double型,直接使用赋值符号“=”,即可完成赋值,如 int a=10: int b: b=a: 即可完成用a给b赋值. 但是对于字符串,这样赋值是不准确的. ...
 - 如何创建一个基于Node的HTTP服务器
			
首先创建一个HTTP服务器. var http = require('http'); function serve(request,response) { console.log(request.me ...
 - ViewPager(视图滑动切换工具)
			
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.Cons ...