转:MySQL InnoDB Add Index实现调研
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实现调研的更多相关文章
- MySQL InnoDB 索引 (INDEX) 页结构
MySQL InnoDB 索引 (INDEX) 页结构 InnoDB 为了不同的目的而设计了不同类型的页,我们把用于存放记录的页叫做索引页 索引页内容 索引页分为以下部分: File Header:表 ...
- Mysql InnoDB三大特性-- 自适应hash index
Mysql InnoDB三大特性-- 自适应hash index
- MySQL 优化之 index merge(索引合并)
深入理解 index merge 是使用索引进行优化的重要基础之一.理解了 index merge 技术,我们才知道应该如何在表上建立索引. 1. 为什么会有index merge 我们的 where ...
- Mysql InnoDB行锁实现方式(转)
Mysql InnoDB行锁实现方式 InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的.InnoDB这种行锁实现特点 ...
- Mysql InnoDB行锁实现方式
Mysql InnoDB行锁实现方式 InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的.InnoDB这种行锁实现特点 ...
- 【mysql】关于Index Condition Pushdown特性
ICP简介 Index Condition Pushdown (ICP) is an optimization for the case where MySQL retrieves rows from ...
- mysql InnoDB 索引小记
0.索引结构 1).MyISAM与InnoDB索引结构比较,如下: 2).MyISAM的索引结构 主键索引和二级索引结构很像,叶子存储的都是索引以及数据存储的物理地址,其他节点存储的仅仅是索引信息.其 ...
- MySQL InnoDB锁机制
概述: 锁机制在程序中是最常用的机制之一,当一个程序需要多线程并行访问同一资源时,为了避免一致性问题,通常采用锁机制来处理.在数据库的操作中也有相同的问题,当两个线程同时对一条数据进行操作,为了保证数 ...
- MySQL InnoDB 修改表列Online DDL
概述 一般来说数据库结构一经设计,不能轻易更改,因为更改DDL(Data Definition Language)操作代价很高,所以在进行数据库结构设计时需要谨慎. 但是业务发展是未知的,特别是那些变 ...
随机推荐
- Eclipse与Tomcat的集成(无插件)
1.下载Eclipse(https://www.eclipse.org/downloads/)和Tomcat(http://tomcat.apache.org/),具体的安装略: 2.打开Eclips ...
- 泛型学习第一天:List与IList的区别 (二)
原文: 探讨Ilist<>与List<> 首先要了解一点的是关于接口的基础知识: 接口不能直接实例化但是接口派生出来的抽象类可以实例化所有派生出来的抽象类都可以强制转换成接口的 ...
- ZooKeeper学习第八期---ZooKeeper伸缩性
转:http://www.cnblogs.com/sunddenly/p/4143306.html 一.ZooKeeper中Observer 1.1 ZooKeeper角色 经过前面的介绍,我想大家都 ...
- iBatis.net 第一篇 搭建
iBatis.net 和 iBatis 是有区别的,一个是在net下使用的,另一个是在java下使用. 要想使用,需要下载相关iBatis.net包 1.下载iBatis.net,在网上有很多下载cs ...
- java——base64 加密和解密
base64 一.加密 *.若有要求输入字符必须为UTF-8: 则需str.getByte("utf-8"); //在getByte()中指定utf-8编码,否则中文字符将被加密 ...
- java:RandomAccessFile随机读取文件内容
RandomAccessFile是用来访问那些保存数据记录的文件的,你就可以用seek( )方法来访问记录,并进行读写了.这些记录的大小不必相同:但是其大小和位置必须是可知的.但是该类仅限于操作文件. ...
- 简述redux(1)
简述redux(1) 概念: 是一个有用的架构,应用场景一般为:多交互.多数据源.如: 某个组件的状态需要共享 某个状态需要在任何地方可以看到 一个组件需要改变全局状态. 一个组件需要改变另一个组件的 ...
- MySQL 基础数据类型优化(如何选择数据类型)
前言: 最近在看高性能 MySQL,记录写学习笔记: 高性能 MySQL 学习笔记(二) Schema与数据类型优化 笔记核心内容:MySQL 如何选择正确的数 ...
- ios --- 调用系统"设置"里的功能(转)
安装后第一次运行软件时,系统会弹出提示用户是否允许软件获取当前位置,如果用户不允许的话,之后运行时系统不会在弹出提示设置,这点很不方便,有个解决办法是给用户一个选项,调出iphone中“设置”定位服务 ...
- Jmeter基本组件
学习jmeter首先配置环境,使工具运行起来,然后需要了解该工具大致的内容,以下是写的Jmeter基本组件 1.添加线程组:右键点击“测试计划”-->“添加”-->“Threads(Use ...