本文从5.1.48下的一个bug说起。前提是新特性fast index creation.
1、现象
5.1.48 (InnoDB plugin 1.0.9) | |
Session1 | Session2 |
create table tb(a int)engine=innodb; insert into tb values(1),(2),(3),(4),(5); |
|
Begin; |
|
select * from tb where a=3; a 3 |
|
| alter table tb add index a(a); |
select * from tb where a=3; ERROR 2013 (HY000): Lost connection to MySQL server during query |
|
说明:session1提示lost connection,实际上MySQL已经dump了。
2、分析
这个结果还是比较好理解的。有fast index creation,默认不需要重作表,因此没有表锁。
Session1开始于加索引之前,第一个select语句使用的是全表扫描,第二个select语句执行时,索引已经建好,所以查询时候使用索引a。这在实现上就可能触发诸多雷区(实际上概念上都已经错误,下文描述)。
在这个版本的实现中,由于新建索引导致第二次select时候使用了prebuilt-> search_tuple. 这个结构在事务开始前没有初始化,因此在尝试使用时类型判断错误直接abort。(row0sel.c)
3、最新版本的实现
由于5.1.48在我们线上用的比较普遍,因此特别查了这个版本的实现和原因。在5.1最新版本中已经避免了这个问题;5.5最新版本也避免了,但有趣的是,两个版本的实现机制完全不同。
我们先列出两个版本的效果再讨论。
5.1.61的效果
5.1.61 (InnoDB plugin 1.0.17) | |
Session1 | Session2 |
create table tb(a int)engine=innodb; insert into tb values(1),(2),(3),(4),(5); |
|
Begin; |
|
select * from tb where a=3; a 3 |
|
| alter table tb add index a(a); |
select * from tb where a=3; ERROR 1412 (HY000): Table definition has changed, please retry transaction |
|
5.5.19的效果
5.5.19 | |
Session1 | Session2 |
create table tb(a int)engine=innodb; insert into tb values(1),(2),(3),(4),(5); |
|
Begin; |
|
select * from tb where a=3; |
|
| alter table tb add index a(a); lock here |
select * from tb where a=3; a 3 |
|
可以看到,两个版本的实现方法不同。5.1里面通过判断索引生成时间与事务开始时间的差别,提示用户需要重启事务。
5.5则是作了个锁升级,锁住加索引操作。并且这个操作不影响session1的一致性读。(当然很容易想到在session1执行一个更新操作是什么效果)。
策略上5.5的实现更符合repeatable-read的概念。
4、其他
说明下,5.1的其他版本未试验。