国内较多的互联网公司都是采用MySQL做为数据库系统,随着业务的发展,不免会碰到须要新建索引来优化某些SQL执行性能的状况。在MySQL实现online create index以前,新建索引意味着业务要中止写入,这是很是影响用户使用体验的,为此,MySQL引入了online create index,极大地减小了业务停写的时间,使得新建索引期间业务可以持续正常的工做。本文主要是对其实现原理的总结以及关键步骤的解释说明。html
在MySQL中表格至少须要设置一个主键,若是用户未指定主键的话,内部会自动生成一个。对于带主键的表格,MySQL会以汇集索引的方式实现,即表格的数据都是完整的存储在汇集索引上的。对于主键的变动,至关于对汇集索引进行变动,这个过程目前MySQL仍是以停写的方式实现的,本文主要讨论的是新建二级索引的实现,为了方便描述,以一个例子来讲明本文要讨论的场景。mysql
create table t1(
c1 int primary key,
c2 int,
c3 int,
);
复制代码
刚开始业务中的SQL都是以主键c1来作查询的,后来随着业务的发展,可能出现了以c2作查询的SQL,此时,为了优化此类SQL的执行性能,须要在c2列上构建索引,即git
create index index_c2 on t1(c2);
复制代码
MySQL online create index主要分为两个阶段,第一阶段为从主表读取索引列并排序生成索引表的数据,称为基线数据;第二阶段为把新建索引阶段索引表的增量数据更新到第一阶段的基线数据上。具体来看,主要过程以下。github
接下来将略过不过重要的步骤1和步骤5,主要描述步骤2-4的详细实现。算法
在执行create index语句以后,MySQL会先等待以前开启的事务先结束后,再真正开始索引的构建工做,这么作的缘由是在执行create index
以前开启的事务可能已经执行过某些更新SQL语句,这些SQL语句没有生成新建索引表的增量数据(Row Log),若是不等待这部分事务结束,可能会出现基线数据中没有此部分数据,且Row Log中也没有此部分数据,最终该部分数据在索引表中不存在。sql
MySQL的等事务结束是经过MDL(Meta Data Lock)实现的,MDL会按序唤醒锁等待者,这样就能保证create index以前开启的事务必定执行完成了。数据库
实际测试中,能够观察到当create index以前的事务一直没有结束时,create index语句会一直卡在thd->mdl_context.upgrade_shared_lock
(sql_table.cc:7381)上。微信
索引构建的第一阶段的工做是根据主表的数据,来构建索引表的数据。此过程总共有两个步骤,第一是读取主表中所须要的索引列数据;第二是将数据按照索引列排序。性能
其中读取主表数据和普通的全表扫描区别不大,而将数据按照索引列排序则是一个外部排序的过程。MySQL对外部排序实现较为简单,仅为最普通的单线程两路归并算法,优势是实现简单,占用内存资源少,缺点是性能较差。测试
通常地,对于数据量较大的表格,构建索引的时间较长,一般是小时级别的,这期间每每会有新事务的提交,其中就可能包含对新建索引表的修改。所以,在索引基线数据构建好以后,还须要把构建期间的增量数据更新到索引表中,那么问题来了,在更新增量数据到索引表中会不断的有新事务修改数据,这样什么时候才能保证全部的修改都更新到索引表上呢?答案是加锁,粗暴一点的加锁方式是在整个增量数据更新到索引表期间停写,完成以后,再放开写入。可是,由于索引构建时间长,增量数据的数据量通常也较大,若是更新整个增量数据到索引表期间都停写的话,会较大地影响用户使用体验。所以,MySQL对加锁过程作了优化。
首先Row Log会被拆分为多个较小的Block,事务的更新会把数据写入到最后一个Block中,所以,普通的DML更新的时候会对最后一个Block加锁。一样的,在更新每一个Block到索引表的时候,会先加锁,若是当前Block不是最后一个Block时,会把锁释放,若是是最后一个Block,则保持加锁状态,直到更新结束。所以,在更新Row Log到索引表期间,加锁的时间比较短,仅在最后一个Block更新到索引表时会持有锁一段时间。
MySQL online create index的总体思路分为两步构建基线以及更新增量,构建基线时采用的归并算法比较简单,资源占用少,但性能会比较差;在更新增量时,采用将增量切分红更小的块,来减小停写的时间,是比较通用的方法。
PS: 本博客更新会在第一时间推送到微信公众号,欢迎你们关注。