文章导读
1. TMySQL概述
2. TMySQL版本信息
3. 创建GCS表
3.1 默认建表
3.2 Compact表转化为GCS表
4. 加字段操作
4.1 在线加字段
4.2 读写并发下的在线加字段
4.3 与一致性读的关系
4.4 约束
5 总结
1. TMySQL概述
MySQL数据库是互娱事务群业务使用最多的数据库产品,截至目前为止,互娱DBA团队已经维护了5000+ MySQL服务器,涉及业务达100+。
然而,MySQL在经历Sun和Oracle的收购后,发展变得较为不明确,互联网上也时常曝出MySQL将会闭源的消息。此外,随着业务量的激增,MySQL某些特性的缺陷对海量数据的运营带来越来越多的困难。因此,越来越有必要去深入研究MySQL的内部原理,并且自行为MySQL添加各项功能和性能优化,提高业务的可用性。
TMySQL是为了适应互娱事业群业务发展需求,由互娱DBA团队定制的一个MySQL分支版本。该分支版本是基于目前比较稳定的MySQL 5.5.24修改而成,目前主要解决了MySQL一直悬而未决的在线加字段问题。
MySQL官方版本的加字段操作需要锁表并拷贝数据,如果表的数据量较大,那么加字段就是一个漫长的过程,并且期间会阻塞所有写操作。这会导致业务的停机时间大大加长,对用户造成影响。
为了解决这个问题,TMySQL对原来InnoDB底层的存储格式进行扩展,设计了新的行格式,在不影响整体性能的情况下,实现快速加字段功能,加字段能迅速完成,可以大大缩短由于原来由于加字段的停机时间。
除了在线加字段,TMySQL未来会根据业务需求进行不断的开发和完善,同时也会整合开源社区各类优秀的patch,致力于打造一个性能更好、功能更强的定制数据库版本。
另外,TMySQL兼容于MySQL 5.5版本,应用程序不需任何改动,并且DB升级成本非常小,并且其特性功能可通过配置参数来启用。
2. TMySQL版本信息
为了区分TMySQL与官方MySQL和其他MySQL分支,TMySQL的版本信息中增加了tmysql和GCS关键字,可通过\s或status命令查看。
mysql> \s
--------------
mysql Ver 14.14 Distrib 5.5.24, for Linux (x86_64) using readline 5.1
Connection id: 1
Current database:
Current user: root@localhost
SSL: Not in use
Current pager: less
Using outfile: ''
Using delimiter: ;
Server version: 5.5.24-tmysql-1.0-log MySQL(GCS) Community Server (GPL)
Protocol version: 10
Connection: Localhost via UNIX socket
Server characterset: utf8
Db characterset: utf8
Client characterset: utf8
Conn. characterset: utf8
UNIX socket: /data1/mysqldata/3306/mysql.sock
Uptime: 25 sec
Threads: 1 Questions: 4 Slow queries: 0 Opens: 33 Flush tables: 1 Open tables: 26 Queries per second avg: 0.160
--------------
目前tmysql有两个版本,分别是1.0和1.1,详见《TMySQL change log》。
另外,也可以使用@@version获取该信息。
mysql> select @@version;
+-----------------------------------+
| @@version |
+-----------------------------------+
| 5.5.24-tmysql-1.0-log |
+-----------------------------------+
1 row in set (0.00 sec)
3. 创建GCS表
3.1 默认建表
InnoDB提供了4中存储格式,分别是Redudant、Compact、Dynamic和Compressed。为了从存储格式上支持在线加字段操作,TMySQL在Compact格式基础上,新增了GCS行格式,并作为TMySQL的默认行格式。
在默认不指定行格式的情况下,创建的InnoDB表都是以GCS为行格式,并称以GCS为行格式的表为GCS表。
mysql> Create Table user(
-> user_id int not null,
-> user_name varchar(25) not null,
-> age int,
-> primary key (user_id),
-> index (user_name)
-> ) engine = innodb;
Query OK, 0 rows affected (0.10 sec)
mysql> show table status like 'user'\G
*************************** 1. row ***************************
Name: user
Engine: InnoDB
Version: 10
Row_format: Gcs
Rows: 0
Avg_row_length: 0
Data_length: 16384
Max_data_length: 0
Index_length: 16384
Data_free: 126877696
Auto_increment: NULL
Create_time: 2012-09-06 12:00:23
Update_time: NULL
Check_time: NULL
Collation: latin1_swedish_ci
Checksum: NULL
Create_options:
Comment:
1 row in set (0.01 sec)
由上述例子知,在TMySQL中没有指定row_format子句所创建的表,默认是GCS行格式。因此,在TMySQL中,以下两建表语句是等价的。
图1 TMySQL建表语句
如果想创建以Compact或其他行格式的表,默认情况下,需要指定建表语句的row_format子句。
mysql> Create Table user_compact(
-> user_id int not null,
-> user_name varchar(25) not null,
-> age int,
-> primary key (user_id),
-> index (user_name)
-> ) engine = innodb row_format=compact;
Query OK, 0 rows affected (0.10 sec)
mysql> show table status like 'user_compact'\G
*************************** 1. row ***************************
Name: user_compact
Engine: InnoDB
Version: 10
Row_format: Compact
Rows: 0
Avg_row_length: 0
Data_length: 16384
Max_data_length: 0
Index_length: 16384
Data_free: 126877696
Auto_increment: NULL
Create_time: 2012-09-06 12:12:19
Update_time: NULL
Check_time: NULL
Collation: latin1_swedish_ci
Checksum: NULL
Create_options: row_format=COMPACT
Comment:
1 row in set (0.01 sec)
默认的GCS行格式支持在线加字段操作,关于GCS格式更详细的内容和实现原理,详见《TMySQL GCS行格式》。
3.2 Compact表转化为GCS表
为了实现MySQL官方版本原地升级为TMySQL,同时享受快速加字段的功能,TMySQL新增的GCS格式完全兼容于Compact,实现Compact表可快速转换为GCS表。
mysql> create table t1(c1 int, c2 varchar(20)) row_format=compact;
Query OK, 0 rows affected (0.10 sec)
#插入100000行数据
mysql> insert into t1 select col1, col2 from big_tb limit 1000000;
Query OK, 1000000 rows affected (12.96 sec)
Records: 1000000 Duplicates: 0 Warnings: 0
mysql> alter table t1 row_format=gcs;
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show table status like 't1'\G
*************************** 1. row ***************************
Name: t1
Engine: InnoDB
Version: 10
Row_format: Gcs
Rows: 1000302
Avg_row_length: 34
Data_length: 34144256
Max_data_length: 0
Index_length: 0
Data_free: 4194304
Auto_increment: NULL
Create_time: 2012-12-04 15:34:38
Update_time: NULL
Check_time: NULL
Collation: utf8_general_ci
Checksum: NULL
Create_options: row_format=GCS
Comment:
1 row in set (0.00 sec)
alter table t1 row_format=gcs;语句没有影响任何行,是一个在线操作,速度也很快。通过这个操作后,t1表就成为了GCS表,支持在线加字段。
mysql> alter table t1 add column (d1 int, d2 varchar(20) not null default 'aaaa');
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
TMySQL支持Compact格式在线转换为GCS,对于官方版本原地升级为TMySQL用重要作用,详见文档《TMySQL升级方式》。
4. 加字段操作
4.1 在线加字段
对于普通的加字段操作,以是否允许为NULL和指定默认值,可分为以下三情况:
1. 允许为NULL并不指定默认值:如Alter Table add int [null]
2. 不允许为NULL并指定默认值:如Alter Table add int not null [default ];
3. 允许为NULL并指定默认值:如Alter Table add int [null] default 。
TMySQL支持前两种的在线加字段操作,第三种只能退化为原锁表的加字段行为(详细原因请参考《TMySQL GCS行格式》)。下面本文通过实例来介绍下在线加字段与普通加字段的差异。
首先,向3.2节中创建的user和user_compact表导入大量数据,如100000000行。(两表分别为GCS表和Compact表)。
然后,分别向user和user_compact执行相同加字段操作。
mysql> alter table user add column (
-> email varchar(50),
-> level int not null default 0
-> );
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> alter table user_compact add column (
-> email varchar(50),
-> level int not null default 0
-> );
Query OK, 100000000 rows affected (26 min 35.61 sec)
Records: 100000000 Duplicates: 0 Warnings: 0
user是GCS表,包含记录100000000行,从结果看,加字段不影响任何行,而且时间仅用了0.02秒。而user_compact表拥有相同的数据,但加字段使用了26分钟,同时是需要拷贝数据的。
下面,通过查询验证在线加字段是有效的。
mysql> select * from user limit 10;
+----------+---------------------------+------+-------+-------+
| user_id | user_name | age | email | level |
+----------+---------------------------+------+-------+-------+
| 1 | 8ebab89612b1e3a6fd80e5ea7 | 0 | NULL | 0 |
| 2 | 4ca0784a03020205a9099d6eb | 0 | NULL | 0 |
| 3 | acd2825c5880182fde6dde33f | 0 | NULL | 0 |
| 4 | 34820f113d544a684657cc02c | 0 | NULL | 0 |
| 5 | 741c91e6ea7d72eb623d70318 | 0 | NULL | 0 |
| 6 | 20b8152068ed3f50301a68a9d | 0 | NULL | 0 |
| 7 | 4280599c6fe5e97dec74ae6d4 | 0 | NULL | 0 |
| 8 | b155c837ec2fe62a3d327758f | 0 | NULL | 0 |
| 9 | 9396f243b8882d8e79575be7e | 0 | NULL | 0 |
| 10 | 618e5393a3156c1086ba59980 | 0 | NULL | 0 |
+----------+---------------------------+------+-------+-------+
10 rows in set (0.00 sec)
mysql> select * from user_compact limit 10;
+----------+---------------------------+------+-------+-------+
| user_id | user_name | age | email | level |
+----------+---------------------------+------+-------+-------+
| 1 | 8ebab89612b1e3a6fd80e5ea7 | 0 | NULL | 0 |
| 2 | 4ca0784a03020205a9099d6eb | 0 | NULL | 0 |
| 3 | acd2825c5880182fde6dde33f | 0 | NULL | 0 |
| 4 | 34820f113d544a684657cc02c | 0 | NULL | 0 |
| 5 | 741c91e6ea7d72eb623d70318 | 0 | NULL | 0 |
| 6 | 20b8152068ed3f50301a68a9d | 0 | NULL | 0 |
| 7 | 4280599c6fe5e97dec74ae6d4 | 0 | NULL | 0 |
| 8 | b155c837ec2fe62a3d327758f | 0 | NULL | 0 |
| 9 | 9396f243b8882d8e79575be7e | 0 | NULL | 0 |
| 10 | 618e5393a3156c1086ba59980 | 0 | NULL | 0 |
+----------+---------------------------+------+-------+-------+
10 rows in set (0.00 sec)
虽然user表没有拷贝数据,但新增字段仍能得到其值(NULL或默认值)。
需要注意的是,即使是GCS表,允许为NULL并指定默认值也不能执行在线加字段的,只能按照原锁表并拷贝数据的方式来执行。
mysql> alter table user add column address varchar(50) default '';
Query OK, 100000000 rows affected (28 min 05.21 sec)
Records: 100000000 Duplicates: 0 Warnings: 0
mysql> select * from user limit 10;
+----------+---------------------------+------+-------+-------+---------+
| user_id | user_name | age | email | level | address |
+----------+---------------------------+------+-------+-------+---------+
| 1 | 8ebab89612b1e3a6fd80e5ea7 | 0 | NULL | 0 | |
| 2 | 4ca0784a03020205a9099d6eb | 0 | NULL | 0 | |
| 3 | acd2825c5880182fde6dde33f | 0 | NULL | 0 | |
| 4 | 34820f113d544a684657cc02c | 0 | NULL | 0 | |
| 5 | 741c91e6ea7d72eb623d70318 | 0 | NULL | 0 | |
| 6 | 20b8152068ed3f50301a68a9d | 0 | NULL | 0 | |
| 7 | 4280599c6fe5e97dec74ae6d4 | 0 | NULL | 0 | |
| 8 | b155c837ec2fe62a3d327758f | 0 | NULL | 0 | |
| 9 | 9396f243b8882d8e79575be7e | 0 | NULL | 0 | |
| 10 | 618e5393a3156c1086ba59980 | 0 | NULL | 0 | |
+----------+---------------------------+------+-------+-------+---------+
10 rows in set (0.00 sec)
由上述例子知,允许为NULL同时指定默认值的加字段操作不能是在线加字段。为了避免这种情况,可以在允许的情况下,改为不允许为NULL并指定默认值,毕竟允许为NULL大多情况下是用处不大的,并且这样就能享受TMySQL在线加字段带来的好处。
另外,TMySQL 1.1开始支持分区表的在线加字段操作,详见《TMySQL分区表支持在线加字段(待发)》。
4.2 读写并发下的在线加字段
TMySQL的在线加字段操作会有一个短暂上锁的过程(详见《TMySQL在线加字段实现原理》)。如果该表上所有读写操作不会长时间持有MDL锁(非慢查询或事务会马上结束),TMySQL支持在大量并发下的在线加字段操作,几乎对表的读写不会造成影响。
例如,表big_tb是一个约50G的compact表,该表上有1000个基于主键的并发查询和更新,并且事务自动提交。下面的例子将Compact表在线修改为GCS表,并且在1000并发读写下执行加字段操作。
mysql> alter table big_tb row_format=GCS;
Query OK, 0 rows affected (0.96 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> alter table big_tb add column (d1 int, d2 varchar(20) not null default 'aa');
Query OK, 0 rows affected (0.72 sec)
Records: 0 Duplicates: 0 Warnings: 0
由测试知,两在线操作都很快完成,并且对并发读写没有带来影响。如果是官方版本的加字段操作,大部分时间该表只能处于只读状态。
但值得注意的是,如果表上大量请求是慢查询或存在长时间的事务,TMySQL的在线加字段操作会尝试等待这些慢查询和事务结束(获得表的字典锁),并且会阻塞后来该表上的新请求(主要是写请求),直到在线加字段操作结束。其原因是在线加字段操作并没有改变原MySQL的并发控制逻辑,只是把这个过程加快了。
当然,官方版本的加字段操作同样存在这个问题,并且加字段所花时间更长,影响范围更广。
4.3 与一致性读的关系
官方版本加字段操作(其他Alter table操作类似)会影响一致性读的数据,例如,一致性备份的过程中,如果执行普通加字段操作,可能会导致无法备份数据。
下面两并发会话模拟官方加字段和在线加字段对一致性读的影响。
会话1
会话2
#创建表并初始化数据
mysql> create table t2(c1 int, c2 int) row_format=gcs;
Query OK, 0 rows affected (0.17 sec)
mysql> insert into t2 values(1,1);
Query OK, 1 row affected (0.00 sec)
mysql> create table t2_compact(c1 int, c2 int) row_format=compact;
Query OK, 0 rows affected (0.06 sec)
mysql> insert into t2_compact values(1,1);
Query OK, 1 row affected (0.00 sec)
#加字段操作
mysql> alter table t2 add column d1 int;
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> alter table t2_compact add column d1 int;
Query OK, 1 row affected (0.14 sec)
Records: 1 Duplicates: 0 Warnings: 0
#模拟一致性备份
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t2;
+------+------+
| c1 | c2 |
+------+------+
| 1 | 1 |
+------+------+
1 row in set (0.01 sec)
mysql> select * from t2_compact;
+------+------+
| c1 | c2 |
+------+------+
| 1 | 1 |
+------+------+
1 row in set (0.00 sec)
mysql> commit;
#先开启事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t2;
+------+------+------+
| c1 | c2 | d1 |
+------+------+------+
| 1 | 1 | NULL |
+------+------+------+
1 row in set (0.00 sec)
#一致性读被破坏!
mysql> select * from t2_compact;
Empty set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
由上述例子知,compact表的一致性读被普通加字段操作破坏了,GCS表使用在线加字段不存在这个问题。
导致这个问题的原因是,事务2先开启事务,在InnoDB内部维护一个更小的trx_id_small。对于compact表,普通加字段操作需要重构数据,记录上的事务信息全部重写,并赋予了一个比trx_id_small大的trx_id_big。当执行一致性读时,InnoDB内部的MVCC判断事务2的trx_id_small比记录上的trx_id_big小,认为该记录是事务2不可见的,最终导致了所有数据都不可见。
而GCS表的在线加字段操作,由于记录没有变化,保留了原来的事务信息,因此不会破坏一致性读。
4.4 约束
要在TMySQL使用快速加字段,必须符合以下约束。
Ø 表必须是GCS表,原Compact表不支持在线加字段功能。
Ø 仅InnoDB存储引擎支持GCS表,其他引擎不支持。
Ø TMySQL 1.1 开始GCS表支持分区表,1.0不支持。
Ø GCS表不支持临时表。
Ø 一次alter table仅允许加一列或多列,但不允许同时进行多个alter table的不同操作(如增删索引、删字段、修改字段等)。
Ø 加字段不支持指定Before或After关键字表示定义在某列之前或之后。
Ø 所加字段不能包含除not null外的任何约束,包括外键约束、唯一约束。
Ø 不支持允许为NULL并指定默认值的加字段操作。
Ø 所加字段不能自增列(auto_increment)
不满足以上约束的都是用原默认的锁表并重构数据的方式来添加字段。
5 总结
TMySQL目前的核心功能是在线加字段,并且1.1版支持了分区表的在线加字段操作。未来TMySQL会引入越来越多的新特性和性能优化,逐渐打造一个性能更好、功能更强的定制数据库版本。
参考:
TMySQL分区表支持在线加字段