MySQL InnoDB Add Index实现调研

MySQL Add Index实现

MySQL各版本,对于add Index的处理方式是不同的,主要有三种:

Copy Table方式

这是InnoDB最早支持的创建索引的方式。顾名思义,创建索引是通过临时表拷贝的方式实现的。

新建一个带有新索引的临时表,将原表数据全部拷贝到临时表,然后Rename,完成创建索引的操作。

这个方式创建索引,创建过程中,原表是可读的。但是会消耗一倍的存储空间

Inplace方式

这是原生MySQL 5.5,以及innodb_plugin中提供的创建索引的方式。所谓Inplace,也就是索引创建在原表上直接进行,不会拷贝临时表。相对于Copy Table方式,这是一个进步。

Inplace方式创建索引,创建过程中,原表同样可读的,但是不可写

Online方式

这是MySQL 5.6.7中提供的创建索引的方式。无论是Copy Table方式,还是Inplace方式,创建索引的过程中,原表只能允许读取,不可写。对应用有较大的限制,因此MySQL最新版本中,InnoDB支持了所谓的Online方式创建索引。

InnoDB的Online Add Index,首先是Inplace方式创建索引,无需使用临时表。在遍历聚簇索引,收集记录并插入到新索引的过程中,原表记录可修改。而修改的记录保存在Row Log中。当聚簇索引遍历完毕,并全部插入到新索引之后,重放Row Log中的记录修改,使得新索引与聚簇索引记录达到一致状态。

与Copy Table方式相比,Online Add Index采用的是Inplace方式,无需Copy Table,减少了空间开销;与此同时,Online Add Index只有在重放Row Log最后一个Block时锁表,减少了锁表的时间。

与Inplace方式相比,Online Add Index吸收了Inplace方式的优势,却减少了锁表的时间。

Inplace add Index

本章节,主要通过测试/源码跟踪的方式,调研InnoDB Inplace Add Index的实现方式。以及分析Inplace add Index有哪些需要注意的地方。

测试准备5.5

测试版本

MySQL 5.5.25

测试表

create table t1 (a int primary key, b int)engine=innodb;

insert into t1 values (1,1),(2,2),(3,3),(4,4);

Inplace Add Index处理流程

SQL

alter table t1 add index idx_t1_b(b);

处理流程

sql_table.cc::mysql_alter_table();

// 判断当前操作是否可以进行Inplace实现,不可进行Inplace Alter的包括:

// 1. Auto Increment字段修改;

// 2. 列重命名;

// 3. 行存储格式修改;等

mysql_compare_tables() -> ha_innobase::check_if_incompatible_data();

// Inplace创建索引第一阶段(主要阶段)

handler0alter.cc::add_index();

// 创建索引数据字典

row0merge.c::row_merge_create_index();

index = dict_mem_index_create();

// 每个索引数据字典上,有一个trx_id,记录创建此索引的事务

// 此trx_id有何功能,接着往下看

index->trx_id = trx_id;

// 读取聚簇索引,构造新索引的项,排序并插入新索引

row0merge.c::row_merge_build_indexes();

// 读取聚簇索引,注意:只读取其中的非删除项

// 跳过所有删除项,为什么可以这么做?往下看

row_merge_read_clustered_index();

// 文件排序

row_merge_sort();

// 顺序读取排序文件中的索引项,逐个插入新建索引中

row_merge_insert_index_tuples();

// 等待打开当前表的所有只读事务提交

sql_base.cc::wait_while_table_is_used();

// 创建索引结束,做最后的清理工作

handler0alter.cc::final_add_index();

// Inplace add Index完毕

Inplace Add Index实现分析

在索引创建完成之后,MySQL Server立即可以使用新建的索引,做查询。但是,根据以上流程,对我个人来说,有三个疑问点:

索引数据字典上,为何需要维护一个trx_id?

trx_id有何作用?

遍历聚簇索引读取所有记录时,为何可跳过删除项?

只读取非删除项,那么新建索引上没有版本信息,无法处理原有事务的快照读;

MySQL Server层,为何需要等待打开表的只读事务提交?

等待当前表上的只读事务,可以保证这些事务不会使用到新建索引

根据分析,等待打开表的只读事务结束较好理解。因为新索引上没有版本信息,若这些事务使用新的索引,将会读不到正确的版本记录。

Online add Index

本章节,主要通过测试/源码跟踪的方式,调研InnoDB Online Add Index的实现方式(MySQL 5.6.7-RC版本提供的新功能)。分析Online add Index有哪些需要注意的地方,最后展示一个InnoDB Online Add Index存在的Bug。

测试准备5.6

测试版本

MySQL 5.6.7-RC

测试表

create table t1 (a int primary key, b int)engine=innodb;

insert into t1 values (1,1),(2,2),(3,3),(4,4);

Online Add Index处理流程

SQL

alter table t1 add index idx_t1_b(b);

处理流程

sql_table.cc::mysql_alter_table();

// 1. 判断当前DDL操作是否可以Inplace进行

check_if_supported_inplace_alter();

// 2. 开始进行Online创建的前期准备工作

prepare_inplace_alter_table();

// 修改表的数据字典信息

prepare_inplace_alter_table_dict();

// 等待InnoDB所有的后台线程,停止操作此表

dict_stats_wait_bg_to_stop_using_tables();

// Online Add Index区别与Inplace Add Index的关键

                // 在Online操作时,原表同时可以读写,因此需要

                // 将此过程中的修改操作记录到row log之中

row0log.cc::row_log_allocate();

row_log_t* log = (row_log_t*)&buf[2 * srv_sort_buf_size];

// 标识当前索引状态为Online创建,那么此索引上的

// DML操作会被写入Row Log,而不在索引上进行更新

dict_index_set_online_status(index, ONLINE_INDEX_CREATION);

// 3. 开始进行真正的Online Add Index的操作(最重要的流程)

inplace_alter_table();

// 此函数的操作,前部分与Inplace Add Index基本一致

// 读取聚簇索引、排序、并插入到新建索引中

// 最大的不同在于,当插入完成之后,Online Add Index

// 还需要将row log中的记录变化,更新到新建索引中

row0merge.cc::row_merge_build_index();

// 在聚簇索引读取、排序、插入新建索引的操作结束之后

// 进入Online与Inplace真正的不同之处,也是Online操作

// 的精髓部分——将这个过程中产生的Row Log重用

row0log.cc::row_log_apply();

// 暂时将新建索引整个索引树完全锁住

// 注意:只是暂时性锁住,并不是在整个重用Row Log的

// 过程中一直加锁(防止加锁时间过长的优化,如何优化?)

rw_lock_x_lock(dict_index_get_lock(new_index));

// InnoDB Online操作最重要的处理流程

// 将Online Copy Table中,记录的Row Log重放到新建索引上

// 重放Row Log的算法如下

// 1. Row Log中记录的是Online创建索引期间,原表上的DML操作

//    这些操作包括:ROW_OP_INSERT;ROW_OP_DELETE_MARK; …

// 2. Row Log以Block的方式存储,若DML较多,那么Row Logs可能

//     会占用多个Blocks。row_log_t结构中包含两个指针:head与tail

//     head指针用于读取Row Log,tail指针用于追加写新的Row Log;

// 3.在重用Row Log时,算法遵循一个原则:尽量减少索引树加锁

//    的时间(索引树加X锁,也意味着表上禁止了新的DML操作)

//     索引树需要加锁的场景

//    (一) 在重用Row Log跨越新的Block时,需要短暂加锁;

//     (二) 若应用的Row Log Block是最后一个Block,那么一直加锁

//         应用最后一个Block,由于禁止了新的DML操作,因此此

//         Block应用完毕,新索引记录与聚簇索引达到一致状态,

//         重用阶段结束;

//    (三) 在应用中间Row Log Block上的row log时,无需加锁,新的

//         DML操作仍旧可以进行,产生的row log记录到最后一个

//         Row Log Block之上;

// 4. 如果是创建Unique索引,那么在应用Row Log时,可能会出现

//     违反唯一性约束的情况,这些情况会被记录到

//     row_merge_dup_t结构之中

row_log_apply_ops(trx, index, &dup);

row_log_apply_op();

row_log_apply_op_low();

// 将New Index的Online row log设置为NULL,

// 标识New Index的数据已经与聚簇索引完全一致

// 在此之后,新的DML操作,无需记录Row Log

dict_index_set_online_status();

index->online_status = ONLINE_INDEX_COMPLETE;

index->online_log = NULL;

rw_lock_x_unlock(dict_index_get_block(new_index));

row_log_free();

// 4. Online Add Index的最后步骤,做一些后续收尾工作

commit_inplace_alter_table();

Online Add Index实现分析

在看完前面分析的InnoDB 5.6.7-RC版本中实现的基本处理流程之后,个人仍旧遗留了几个问题,主要的问题有:

Online Add Index是否支持Unique索引

确切的答案是:支持(不过存在Bug,后面分析)。InnoDB支持Online创建Unique索引。

既然支持,就会面临Check Duplicate Key的问题。Row Log中如果存在与索引中相同的键值怎么处理?怎么检测是否存在相同键值?

InnoDB解决此问题的方案也比较简介易懂。其维护了一个row_merge_dup_t的数据结构,存储了在Row log重放过程中遇到的违反唯一性冲突的Row Log。应用完Row Log之后,

外部判断是否存在Unique冲突(有多少Unique冲突,均会记录),Online创建Unique索引失败。

Row Log是什么样的结构,如何组织的

在Online Add Index过程中,并发DML产生的修改,被记录在Row Log中。首先,Row Log不是InnoDB的Redo Log,而是每个正在被Online创建的索引的独占结构。

Online创建索引,遵循的是先创建索引数据字典,后填充数据的方式。因此,当索引数据字典创建成功之后,新的DML操作就可以读取此索引,尝试进行更新。但是,由于索引结构上

的status状态为ONLINE_INDEX_CREATION,因此这些更新不能直接应用到新索引上,而是放入Row Log之中,等待被重放到索引之上。

Row Log中,以Block的方式管理DML操作内容的存放。一个Block的大小为由参数innodb_sort_buffer_size控制,默认大小为1M (1048576)。初始化阶段,Row Log申请两个这样的Block。

在Row Log重放的过程中,到底需要多久的锁表时间

前面的流程分析中,也提到了锁表的问题(内部为锁新建索引树的操作实现)。

在重放Row log时,有两个情况下,需要锁表:

情况一:在使用完一个Block,跳转到下一个Block时,需要短暂锁表,判断下一个Block是否为Row Log的最后一个Block。若不是最后一个,跳转完毕后,释放锁;使用Block内的row log不加锁,用户DML操作仍旧可以进行。

情况二:在使用最后一个Block时,会一直持有锁。此时不允许新的DML操作。保证最后一个Block重放完成之后,新索引与聚簇索引记录达到一致状态。

Online Add Index是否存在Bug?

答案同样是肯定的,存在Bug。

其中有一个Bug,重现方案如下:

create table t1 (a int primary key, b int, c char(250))engine=innodb;

insert into t1(b,c) values (1,’aaaaaaa’);

// 保证数据量够多

insert into t1(b,c) select b,c from t1;

insert into t1(b,c) select b,c from t1;

insert into t1(b,c) select b,c from t1;

// max(a) = 196591

select max(a) from t1;

// b中同样没有相同项

update t1 set b = a;

session 1                                                                     session 2

alter table t1 add unique index idx_t1_b(b);

insert into t1(b,c) values (196592,’b’);

// 此update,会产生b=196589的重复项

update t1 set b=196589 where a=196582;

delete from t1 where a = 262127;

在以上的测试中,首先为表准备足够的数据,目的是session 1做Online Add Index的读取聚簇索引阶段,session 2新的记录也能够被读到。

在session 1的Online Add Index完成之后(成功),执行以下两个命令,结果如下

mysql> show create table t1;

+——-+————————————————–

| Table | Create Table

+——-+————————————————–

| t1 | CREATE TABLE `t1` (

`a` int(11) NOT NULL AUTO_INCREMENT,

`b` int(11) DEFAULT NULL,

`c` char(250) DEFAULT NULL,

PRIMARY KEY (`a`),

UNIQUE KEY `idx_t1_b` (`b`)

) ENGINE=InnoDB AUTO_INCREMENT=262129 DEFAULT CHARSET=gbk |

+——-+————————————————–

mysql> select * from t1 where a in (196582,196589);

+——–+——–+———+

| a | b | c |

+——–+——–+———+

| 196582 | 196589
| aaaaaaa |

| 196589 | 196589
| aaaaaaa |

+——–+——–+———+

2 rows in set (0.04 sec)

可以看到,b上已经有了一个Unique索引,但是表中却存在两个相同的取值为196589的值。

此Bug,是处理Row Log的重放过程,未详尽考虑所有情况导致的。因此,在MySQL 5.6版本稳定之前,慎用!

--此文章转载自登博的博客,给大家分享。

转:MySQL InnoDB Add Index实现调研的更多相关文章

  1. MySQL InnoDB 索引 (INDEX) 页结构

    MySQL InnoDB 索引 (INDEX) 页结构 InnoDB 为了不同的目的而设计了不同类型的页,我们把用于存放记录的页叫做索引页 索引页内容 索引页分为以下部分: File Header:表 ...

  2. Mysql InnoDB三大特性-- 自适应hash index

    Mysql InnoDB三大特性-- 自适应hash index

  3. MySQL 优化之 index merge(索引合并)

    深入理解 index merge 是使用索引进行优化的重要基础之一.理解了 index merge 技术,我们才知道应该如何在表上建立索引. 1. 为什么会有index merge 我们的 where ...

  4. Mysql InnoDB行锁实现方式(转)

    Mysql InnoDB行锁实现方式 InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的.InnoDB这种行锁实现特点 ...

  5. Mysql InnoDB行锁实现方式

    Mysql InnoDB行锁实现方式 InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的.InnoDB这种行锁实现特点 ...

  6. 【mysql】关于Index Condition Pushdown特性

    ICP简介 Index Condition Pushdown (ICP) is an optimization for the case where MySQL retrieves rows from ...

  7. mysql InnoDB 索引小记

    0.索引结构 1).MyISAM与InnoDB索引结构比较,如下: 2).MyISAM的索引结构 主键索引和二级索引结构很像,叶子存储的都是索引以及数据存储的物理地址,其他节点存储的仅仅是索引信息.其 ...

  8. MySQL InnoDB锁机制

    概述: 锁机制在程序中是最常用的机制之一,当一个程序需要多线程并行访问同一资源时,为了避免一致性问题,通常采用锁机制来处理.在数据库的操作中也有相同的问题,当两个线程同时对一条数据进行操作,为了保证数 ...

  9. MySQL InnoDB 修改表列Online DDL

    概述 一般来说数据库结构一经设计,不能轻易更改,因为更改DDL(Data Definition Language)操作代价很高,所以在进行数据库结构设计时需要谨慎. 但是业务发展是未知的,特别是那些变 ...

随机推荐

  1. KestrelHttpServer

    source code of Kestrel of documentation https://github.com/aspnet/KestrelHttpServer https://github.c ...

  2. mybatis 中if标签判断boolean 的写法。

    mybatis 的if 比较标签在比较数值时可以这样写: <if test="value=0"> </if> 在比较字符串时可以这么写: <if te ...

  3. Ubuntu下用crontab 部署定时任务

    用php做了一个网站,其中一个统计工能,需要每周定时用行.想看看有什么方法,之前看别人的东西,一般有2中方式,一个是php自带的定时任务,一个是用系统 带的,linux下的crontab和window ...

  4. External (and Live) snapshots with libvirt

    list all the block devices associated with the guest $ virsh domblklist testvm --details Type Device ...

  5. JQuery调用iframe子页面函数/对象的方法例子

    父页面有个ID为mainfrm.name为Iframe1的iframe,iframe连接b.html,该页面有个函数test 在父页面调用b.html的test方法为: $("#mainfr ...

  6. python的常见排序

    在python程序中,我们往往自始至终都在与序列(列表.字典.元组)打交道,而最常用的操作就是对序列排序了.在此简单总结一下python中的排序. 基本排序方法 在python中,list对象具有 s ...

  7. Python的常见几道数学运算题

    一 python 相关数学函数及使用示例 常用的数学函数: ceil(x) 取顶 floor(x) 取底 fabs(x) 取绝对值 factorial (x) 阶乘 hypot(x,y)  sqrt( ...

  8. Announcing the Release of ASP.NET MVC 5.1, ASP.NET Web API 2.1 and ASP.NET Web Pages 3.1 for VS2012

    The NuGet packages for ASP.NET MVC 5.1, ASP.NET Web API 2.1 and ASP.NET Web Pages 3.1 are now live o ...

  9. Python爬虫之利用正则表达式爬取内涵吧

    首先,我们来看一下,爬虫前基本的知识点概括 一. match()方法: 这个方法会从字符串的开头去匹配(也可以指定开始的位置),如果在开始没有找到,立即返回None,匹配到一个结果,就不再匹配. 我们 ...

  10. nginx安装目录

    1.rpm -ql nginx看看通过yum安装到哪里了 2./etc/logrotate.d/nginx    配置 nginx日志轮转 用于logrotate服务的日志切割 3./etc/ngin ...