导读:在MySQL5.6之前版本,Innodb表的许多DDL操作是非常昂贵。许多ALTER TABLE操作的原理是通过创建新的空表,定义被要求的表选项和索引,然后逐行拷贝已存在记录到新表,在插入行时更新索引。在旧表所有行被拷贝完,旧表被删除和那新表被重命名为旧的表名。MySQL5.5,和MySQL5.1 有了InnoDB Plugin,优化了CREATE INDEX和DROP INDEX 避免表的拷贝行为。这个特性被称为Fast index Creation。MySQL 5.6 加强ALTER TABLE操作许多方式来避免拷贝表。另外加强的允许SELECT查询和INSERT, UPDATE,和DELETE(DML)语句被处理当该表正被修改时。这些功能的组合现在被称为online DDL。

 

语法解读:online DDL操作允许alter table 语句指定ALGORITHM,LOCK 子句。

ALGORITHM=inplace,表示DDL操作在原表上就地操作,ALGORITHM=copy ,表示采用以前方式(复制表)进行DDL 操作。DDL没有指定ALGORITHM子句,MySQL会自动选择每种DDL所允许的ALGORITHM子句(并不是所有DDL在所有情况下都支持每种ALGORITHM,如果两种都支持,默认ALGORITHM=inplace优先选择,如果指定某种DDL在某种情况下不允许的ALGORITHM,该执行的DDL 以失败告终)。

LOCK={default|none|shared|exclusive|}是显式指定alter table 的锁级别,default 表是使用所有DDL在不同情况下的默认的锁级别(也是DDL在不同情况下支持最低级别的锁)。LOCK=none不加锁,允许DML,查询并发(注意LOCK=none不是真正表示执行DDL操作不进行锁,在DDL执行真正执行时,还是加排他锁)。LOCK=SHARED不允许DML并发,允许查询。LOCK=exclusive 不允许 DML并发和查询。(alter table 没有显式指定锁的级别,默认情况使用该DDL允许的默认的最低级别锁<也就是看情况>,如果显式指定的锁是该DDL不允许的级别,该DDL以失败中止)

 

online DDL工作机制:通过ALGORITHM实现DDL操作是复制表还是在原表就地操作,让DDL尽量影响那些需要改变的数据,而不要操作那些不需改变的数据如:对一列设置默认值,仅对该表.frm修改而涉及数据文件。通过LOCK 实现DML,查询的并发。不管锁是哪种级别,在DDL语句正在准备时和在DDL提交时都是以独占方式访问表。而LOCK不同的级别控制是否允许在online DDL操作之前已发起的DML和查询但没有提交和回滚的事务并发。

 

DDL的影响范围:有些DDL操作仅影响逻辑文件(对一列设置默认值,仅对该表.frm修改不是数据文件。),有些DDL操作影响整个数据文件(重建主键,需要对整个数据进行新的聚集组织),有些DDL操作影响部分数据文件(新增二级索引,二级索引只有主键的值,只需对主键的值进行重构)。

 

online DDL执行时间:从上面的分析,我可以知道online DDL的执行时间的长短取决多个方面:

1:online DDL 操作的影响范围的大小。

2:online DDL 操作对象的数据量的大小。

3:online DDL 操作时,当前是否已存在需长时间执行的事务

4:online DDL 操作选取ALGORITHM(inplace不一定比copy快)和LOCK的方式

以上因素要综合考虑,才能使online DDL 操作更顺利和更高效。

 

OnLine DDL后innodb的索引

5.0和更早版本的MySQL中,在一个已经有很多数据的表上添加或者删除一个索引将非常耗时。CREATE INDEX和DROP INDEX通过创建一个新的空的带有要创建索引的表,然后拷贝存在的行到新表中,同时更新索引,当此时key没有排序时插入条目极慢。在所有的行都被拷贝完成以后,旧表被删除,新表被改名。

一条ALTER TABLE命令创建多个索引是可能的。这相对有效,因为聚簇索引只需要被扫描一次。比如

CREATE TABLE T1(A INT PRIMARY KEY, B INT, C CHAR(1)) ENGINE=InnoDB;

INSERT INTO T1 VALUES

(1,2,'a'), (2,3,'b'), (3,2,'c'), (4,3,'d'), (5,2,'e');

COMMIT;

ALTER TABLE T1 ADD INDEX (B), ADD UNIQUE INDEX (C);

上面的命令将创建表T1,在列A上创建聚簇的主键索引,插入几行,然后在列B和C上创建2个新的索引。如果在ALTER TABLE命令之前有很多行,这个方法将比预先创建带有全部索引的表然后加载数据有效。

你也可以一次只创建一个索引,但是每次聚簇索引都会针对CREATE INDEX扫描一次。在InnoDB中删除索引并不要求表数据的任何拷贝,因此一次删多条或者一次删一条是等价的。

重建InnoDB中的聚簇索引总是要求拷贝表中的数据。比如,如果你创建一个表时没有主键,InnoDB将为你选择一个,它可能是第一个UNIQUE key或者NOT NULL的列,或者一个系统产生的key。如果你在后来定义了一个PRIMARY KEY,数据将不得不拷贝。

2.3 实现

InnoDB有2种类型的索引:聚簇索引和二类索引,由于聚簇索引包含数据值于Btree节点之中,添加或者删除一个聚簇索引涉及到数据的拷贝,并将创建一个新的表的拷贝。一个二类索引实际上只包含索引的key和主键的值,这种类型的索引可以在不用拷贝聚簇索引中数据的情况下创建或者删除。进一步,因为二类索引包含着主键的值,当你改变主键定义的时候,将重新创建聚簇索引,所有的二级索引也将重新创建。

删除一个二类索引是简单的,只有内部的InnoDB系统表和MySQL的数据字典表需要被更新来反应索引不再存在的事实。InnoDB将返回索引所使用的空间给表空间,因此新索引或者额外表的行可以使用这些空间。

要添加一个二类索引到存在的表中,InnoDB扫描表,使用内存Buffer和临时文件按照被索引的列的顺序对行进行排序。然后Btree就被建立起来了,这比以随机顺序插入行时保证key的值有序要有效。 因为Btree节点在满了以后会分裂,按照此种方式构建的索引有一个较高的填充因子,对随后的访问具有较好的效率。

2.4 并发的考虑

测试表明对于主键的修改操作,仅rename阶段会短暂的排它锁。

mysql> drop index `PRIMARY` on t1;
Query OK, 300028 rows affected (14.76 sec)
Records: 300028 Duplicates: 0 Warnings: 0

mysql> show profile for query 43;
+----------------------+-----------+
| Status | Duration |
+----------------------+-----------+
| starting | 0.000080 |
| checking permissions | 0.000012 |
| init | 0.000004 |
| Opening tables | 0.000230 |
| setup | 0.000036 |
| creating table | 0.049951 |
| After create | 0.000301 |
| System lock | 0.282387 |
| copy to tmp table | 14.089265 |
| rename result table | 0.332012 |
| end | 0.000036 |
| query end | 0.000012 |
| closing tables | 0.000090 |
| freeing items | 0.003100 |
| cleaning up | 0.000061 |
+----------------------+-----------+
15 rows in set, 1 warning (0.00 sec)

mysql> select * from t1 limit 10;
...
mysql> show profile for query 28;
+--------------------------------+----------+
| Status | Duration |
+--------------------------------+----------+
| starting | 0.000081 |
| checking permissions | 0.000013 |
| Opening tables | 0.000022 |
| Waiting for table metadata loc | 0.254933 |
| Opening tables | 0.001558 |
| init | 0.000070 |
| System lock | 0.000026 |
| optimizing | 0.000013 |
| statistics | 0.000040 |
| preparing | 0.000035 |
| executing | 0.000008 |
| Sending data | 0.000173 |
| end | 0.000014 |
| query end | 0.000032 |
| closing tables | 0.000032 |
| freeing items | 0.000384 |
| cleaning up | 0.000036 |
+--------------------------------+----------+
17 rows in set, 1 warning (0.00 sec)

右边session2中前面在drop index之后执行了很多条查询,都没有阻塞,仅在session中的rename阶段短暂阻塞。

由于Online DDL原因,DML操作也与上面类似,不会长阻塞。但是如果例如drop primay key等操作没有使用online ddl特性,依然要copy to tmp table,那么依然会像myisam一样对DML阻塞。

对于myisam表创建index时<copy to tmp table>,select只有rename时段堵塞,DML被全程阻塞。