[MySQL5.7] Innodb的索引锁优化

背景:

.

这是一个臭名昭彰的问题,Innodb的btree发生合并/分裂等可能修改B-tree的操作时,都需要对其加排他的索引锁,这时候是无法对该索引进行读写操作的,极大的影响了性能;关于index lock,可以看看大神Domas的这篇博文:“Innodb locking makes me sad”  以及Vadim的这篇博客

 .

总而言之,MySQL5.7.2的这个功能点的改进是万众期待的!

 .

以下是阅读Rev6232的笔记,大概理了下关于索引锁优化的几个点。有些只是自己的理解,可能还需要仔细求证; 写的比较乱,同学们慎入- -!!

 .

1.新的读写锁类型:SX锁

 .

在之前的版本中,Innodb层有两种锁类型,一种是S共享锁,一种是X排他锁,在5.7.2增加了一种新的读写锁类型称为SX共享排他锁,这三类锁的关系为:

        | S|SX| X|

      –+–+–+–+

       S| o| o| x|

      –+–+–+–+

      SX| o| x| x|

      –+–+–+–+

       X| x| x| x|

      –+–+–+–+

 .

S锁和X锁与之前的逻辑相同,没有做变动

SX与SX和X互斥,与S共享,内部定义为RW_SX_LATCH,根据描述,在加上SX锁之后,不会影响读操作,但阻塞写操作

对应的加SX锁接口函数:rw_lock_sx_lock_func ,sx 的 lock_word取值是X lock的一半(X_LOCK_HALF_DECR).LOCK_WORD的范围代表的锁的含义见文件sync/sync0rw.cc的注释

加锁逻辑比较简单,同样使用lock_word来判断,锁之间的冲突关系参阅函数rw_lock_sx_lock_low;加锁失败的话会去做spin,然后再retry,逻辑还是比较简单的。

 .

SX的定义感觉有点类似seqlock,读写不阻塞,但写写阻塞;

 .

那么目前的逻辑中,有哪些情况要会用到SX锁呢,简单的玩了一下,大概有这么几个地方会用到:

#1

fsp_header_init:

    buf_page_get(space, zip_size, 0, RW_SX_LATCH, mtr);

当新create一个表时,会调用这个函数初始化space header,读入第0号page时使用SX lock

 .

fsp_header_init->fsp_fill_free_list:

1148                         /* We are going to initialize a new descriptor page
1149                         and a new ibuf bitmap page: the prior contents of the
1150                         pages should be ignored. */
1151
1152                         if (i > 0) {
1153                                 block = buf_page_create(
1154                                         space, i, zip_size, mtr);
1155                                 buf_page_get(space, zip_size, i,
1156                                              RW_SX_LATCH, mtr);

 .

>fsp_get_space_header

>fsp_alloc_seg_inode

>xdes_lst_get_descriptor

566         descr = fut_get_ptr(space, zip_size, lst_node, RW_SX_LATCH, mtr)
567                 – XDES_FLST_NODE;

>fseg_alloc_free_page_low

 .

看来innodb文件系统这块大量使用了SX锁

 .

>btr_root_get

根page加SX锁

 .

#2

dict_stats_analyze_index

在更新索引统计信息时,先给索引加个S锁获取btree的page数,然后在mtr_commit后,再加索引上的sx锁(mtr_sx_lock) ,随后计算索引上的统计信息

 .

#3

buf_flush_page:

当需要将page刷到磁盘时,加的是SX锁,这样在写磁盘的过程中,其他用户线程也可以读该block(但只对非压缩表生效)

 .

#4

btr_cur_search_to_nth_level:

779         switch (latch_mode) {

780         case BTR_MODIFY_TREE:
781                 /* Most of delete-intended operations are purging.
782                 Free blocks and read IO bandwidth should be prior
783                 for them, when the history list is glowing huge. */
784                 if (lock_intention == BTR_INTENTION_DELETE
785                     && trx_sys->rseg_history_len > BTR_CUR_FINE_HISTORY_LENGTH
786                     && buf_get_n_pending_read_ios()) {
787                         mtr_x_lock(dict_index_get_lock(index), mtr);
788                 } else {
789                         mtr_sx_lock(dict_index_get_lock(index), mtr);
790                 }
791                 upper_rw_latch = RW_X_LATCH;
792                 break;

当latch_mode为BTR_MODIFY_TREE时,需要对BTREE做操作,这里分两种情况:当purge操作处于比较繁忙的状态时,直接加索引X锁,退化到之前版本的状态,否则加SX锁,在修改BTREE时不阻塞读;

 .

在代码的注释中,有对设定退化的解释:

 .

/** For the index->lock scalability improvement, only possibility of clear

performance regression observed was caused by grown huge history list length.

That is because the exclusive use of index->lock also worked as reserving

free blocks and read IO bandwidth with priority. To avoid huge glowing history

list as same level with previous implementation, prioritizes pessimistic tree

operations by purge as the previous, when it seems to be growing huge.

 Experimentally, the history list length starts to affect to performance

throughput clearly from about 100000. */

#define BTR_CUR_FINE_HISTORY_LENGTH    100000

 .

 .

另外简单解释下,对于DML操作,总是先是乐观操作,即假定不会导致BTREE分裂/合并,如果判断乐观操作失败,则进行悲观操作,前者的LATCH_MODE类型为BTR_MODIFY_LEAF,后者为BTR_MODIFY_TREE (见函数row_ins_clust_index_entry)

BTR_MODIFY_TREE 不会使用ADAPTIVE HASH.

 .

2. BTREE操作意向类型(姑且这么翻译吧…)

 .

在MySQL5.7里增加了这么一组定义

 .

  83 enum btr_intention_t {
84         BTR_INTENTION_DELETE,
85         BTR_INTENTION_BOTH,
86         BTR_INTENTION_INSERT
87 };

 .

 .

根据latch_mode,调用函数btr_cur_get_and_clear_intention来设定。

 .

新增定义BTR_LATCH_FOR_INSERT和BTR_LATCH_FOR_DELETE,这两个宏决定了btr_intention的值,其作用是用于在需要修改BTREE时,优化块锁的范围;

 .

简单grep了一下代码,这两个标记是在做purge操作或者online ddl后的merge数据,或者在Undo回滚某些更改时会用到;我做的简单的插入操作则是BTR_INTENTION_BOTH

 .

在检索BTREE时, btr_intention可能会发生变化,以确保所有潜在会被修改的block被latch住。

 .

3.新增的latch_mode类型

几种latch mode,相比5.6,MySQL5.7.2增加了BTR_MODIFY_TREE和BTR_CONT_SEARCH_TREE

.
/** Latching modes for btr_cur_search_to_nth_level(). */

enum btr_latch_mode {

        /** Search a record on a leaf page and S-latch it. */

        BTR_SEARCH_LEAF = RW_S_LATCH,

        /** (Prepare to) modify a record on a leaf page and X-latch it. */

        BTR_MODIFY_LEAF = RW_X_LATCH,

        /** Obtain no latches. */

        BTR_NO_LATCHES = RW_NO_LATCH,

        /** Start modifying the entire B-tree. */

        BTR_MODIFY_TREE = 33,

        /** Continue modifying the entire B-tree. */

        BTR_CONT_MODIFY_TREE = 34,

        /** Search the previous record. */

        BTR_SEARCH_PREV = 35,

        /** Modify the previous record. */

        BTR_MODIFY_PREV = 36,

        /** Start searching the entire B-tree. */

        BTR_SEARCH_TREE = 37,

        /** Continue searching the entire B-tree. */

        BTR_CONT_SEARCH_TREE = 38

};

 .

对于BTR_SEARCH_TREE,只在需要扫描btree计算统计信息时需要用到,调用函数:

#dict_stats_analyze_index_level

#dict_stats_analyze_index_for_n_prefix

在使用BTR_SEARCH_TREE来搜索btree时,已经在btree上持有了S锁(BTR_ALREADY_S_LATCHED),该部分暂不展开;

 .

BTR_CONT_SEARCH_TREE 的使用封装在函数btr_page_get_father_node_ptr_for_validate中;

调用栈为btr_page_get_father_node_ptr_for_validate <- btr_validate_level <-btr_validate_index, 当执行类似CHECK TABLE这样的操作时会触发这部分逻辑。

 .

4.索引锁的优化逻辑

.

先简单的理一下MySQL5.6的btr_cur_search_to_nth_level函数逻辑,然后再看看5.7的改变;

 .

4.1 MySQL5.6的索引检索大概流程为:

 .

a.首先当latch mode为BTR_MODIFY_TREE 直接给BTREE加X latch, 对于BTR_CONT_MODIFY_TREE,需要保证X LATCH已经加好 ,其他latch_mode类型,则对索引加S LATCH

 .

b.设定 height = ULINT_UNDEFINED;  初始化检索,表示从根节点开始

 .

c.准备读page,设定将要读进buffer pool的page的latch类型;

当当前处于非叶子节点时,设定rw_latch为RW_NO_LATCH,表示不加latch;

当处于叶子节点时,对于BTR_MODIFY_LEAF,加RW_X_LATCH,对于BTR_SEARCH_LEAF,加RW_S_LATCH,并且会去判断当前的操作是否可以使用change buffer(ibuf_should_try)

 .

然后通过buf_page_get_gen读取需求的page ; (当可以使用change buffer时,如果page不在bp时,则调用change buffer的相关接口函数完成操作,直接返回)

这个page会根据之前设定的rw_latch加对应的latch类型;

 .

 .

d.如果当前读到的是叶子节点:

需要根据latch_mode来对Page加锁,具体见函数btr_cur_latch_leaves,加锁规则为:

BTR_SEARCH_LEAF:当前page加RW_S_LATCH

BTR_MODIFY_LEAF:当前page加RW_X_LATCH

BTR_MODIFY_TREE:当前page 及左右page,都需要加RW_X_LATCH

BTR_SEARCH_PREV:当前page及左节点加RW_S_LATCH

BTR_MODIFY_PREV: 当前page及左节点加RW_X_LATCH

 .

对于无需修改btree的操作,释放索引上的S锁(如果是在当前函数开始时锁上的索引S锁);

.

e.检查page内匹配的记录;

调用:

page_cur_search_with_match(

                block, index, tuple, page_mode, &up_match, &up_bytes,

                &low_match, &low_bytes, page_cursor);

在page内进行二分查找,找到对应的记录;暂时先不展开;

 .

 .

f.如果当前btree的level还没到达需求的层次:

准备检索下一层;height—

获取下一层的page no(btr_node_ptr_get_child_page_no)

返回c

 .

g.需求的level不是叶子节点,加X LATCH;对于叶子节点,还可能需要更新自适应哈希(btr_search_info_update)

 .

4.2. MySQL5.7的索引检索优化(只考虑MODIFY TREE的场景)

 .

#在purge带来的压力不大的情况下,modify tree操作只加SX锁,这意味着可以同时对索引进行读操作;

 .

#每往下层读一个page,都保存其savepoint到一个数组tree_savepoints中,读入的block维持在数组tree_blocks中;

 .

#在将page读入bp时,如果page还未到达目标层,读入时不加LATCH

 .

#对于叶子节点,还需要对当前page,及当前page的左右节点都加上X LATCH(btr_cur_latch_leaves)

 .

#如果当前检索的level还没有到达需求的层次:

>>如果当前的lock_intention是BTR_INTENTION_DELETE时,悲观删除操作,可能导致节点操作,释放所有占用的block的LATCH,将lock_intention更改为BTR_INTENTION_BOTH,并重新检索BTREE;

>>对于非唯一索引,如果当前匹配的node_ptr是page的第一个或者最后一个记录,或者最后一个/第一个记录的键值和node_ptr相匹配,将detected_same_key_root设置为TRUE;

这里没怎么看明白,根据注释,这种场景下如果latch mode为BTR_CONT_MODIFY_TREE,可能会选择到另外一个page,因此不可以释放节点的latch,避免死锁(阻塞另外一个检索相同key的线程)

 .

#当detected_same_key_root为false时,如果对当前page的操作可能导致潜在的btree修改(通过函数btr_cur_will_modify_tree判断,记录过少,或者page过满),则不可以释放上层的latch,如果不会导致潜在btree修改,则将当前page的上层page unpin掉(递减buf_fix_count),同时递增n_releases(从n_releases往前的在tree_blocks中记录的block都无需latch)

 .

#如果下一次要读的page到达目标level,对ROOT PAGE加SX LATCH,并将从n_releases到当前在tree_blocks数组中记录的block都X LATCH住(mtr_block_x_latch_at_savepoint)

 .

简而言之,当需要做悲观DML时,会对索引加SX锁,同时所有可能会导致btree修改的page都被X LATCH住,这样就将索引操作影响的范围限定在特定的page,其他page的读操作将不受影响

时间: 2024-08-04 14:27:33

[MySQL5.7] Innodb的索引锁优化的相关文章

PostgreSQL 10 GIN索引 锁优化

标签 PostgreSQL , gin , 倒排索引 , 全文检索 , 性能优化 背景 PostgreSQL gin索引接口常被用于多值列的检索,例如全文检索类型.数组类型. 有兴趣了解更多索引接口的原理和使用场景,可以参考下文. <PostgreSQL 9种索引的原理和应用场景> 今天要说道一下PostgreSQL GIN索引的代码优化. 在说GIN代码优化前,我们先来看一个场景,以及在老版本下的性能表现. 例子 创建一张测试表,三个字段,其中一个全文检索字段,另一个PK,还有一个时间. 全

理解MySQL——索引与优化

写在前面:索引对查询的速度有着至关重要的影响,理解索引也是进行数据库性能调优的起点.考虑如下情况,假设数据库中一个表有10^6条记录,DBMS的页面大小为4K,并存储100条记录.如果没有索引,查询将对整个表进行扫描,最坏的情况下,如果所有数据页都不在内存,需要读取10^4个页面,如果这10^4个页面在磁盘上随机分布,需要进行10^4次I/O,假设磁盘每次I/O时间为10ms(忽略数据传输时间),则总共需要100s(但实际上要好很多很多).如果对之建立B-Tree索引,则只需要进行log100(

浅谈MyISAM 和 InnoDB 的区别与优化_Mysql

MyISAM 和 InnoDB 的基本区别 1.InnoDB不支持FULLTEXT类型的索引. 2.InnoDB 中不保存表的具体行数,也就是说,执行select count(*) from table时,InnoDB要扫描一遍整个表来计算有多少行,但是MyISAM只要简单的读出保存好的行数即可.注意的是,当count(*)语句包含 where条件时,两种表的操作是一样的. 3.对于AUTO_INCREMENT类型的字段,InnoDB中必须包含只有该字段的索引,但是在MyISAM表中,可以和其他

MySQL 5.7下InnoDB对COUNT(*)的优化

0.导读 饱受诟病的InnoDB表COUNT(*)性能问题在5.7下做了优化,果真如此吗? 1.经典需求:InnoDB表COUNT(*) InnoDB引擎表经常被抱怨执行COUNT(*)的效率太差,因此此类需求通常会被建议用其他方法来满足,比如另外加一个计数器表,或者用SHOW TABLE STATUS查看大概数量. 不过,从MySQL 5.7.2起,这个问题得到了解决,我们来看看. 2.MySQL 5.7版本InnoDB对COUNT(*)的优化 MySQL每发布一个新版本,都会放出相应的Rel

JVM中锁优化,偏向锁、自旋锁、锁消除、锁膨胀

本文将简单介绍HotSpot虚拟机中用到的锁优化技术. 自旋锁 互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性能带来了很大的压力.而在很多应用上,共享数据的锁定状态只会持续很短的一段时间.若实体机上有多个处理器,能让两个以上的线程同时并行执行,我们就可以让后面请求锁的那个线程原地自旋(不放弃CPU时间),看看持有锁的线程是否很快就会释放锁.为了让线程等待,我们只须让线程执行一个忙循环(自旋),这项技术就是自旋锁. 如果锁长时间被占用,

lucene索引文件大小优化小结

   随着业务快速发展,基于lucene的索引文件zip压缩后也接近了GB量级,而保持索引文件大小为一个可以接受的范围非常有必要,不仅可以提高索引传输.读取速度,还能提高索引cache效率(lucene打开索引文件的时候往往会进行缓存,比如MMapDirectory通过内存映射方式进行缓存).       如何降低我们的索引文件大小呢?本文进行了一些尝试,下文将一一介绍. 1 数值数据类型索引优化 1.1 数值类型索引问题         lucene本质上是一个全文检索引擎而非传统的数据库系统

jvm(13)-线程安全与锁优化(转)

0.1)本文部分文字转自"深入理解jvm", 旨在学习 线程安全与锁优化 的基础知识: 0.2)本文知识对于理解 java并发编程非常有用,个人觉得,所以我总结的很详细: [1]概述 [2]线程安全 1)线程安全定义:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的:(干货--线程安全定义) [2.1]java 语言中的线程安全(干货-

《高并发Oracle数据库系统的架构与设计》一2.3 索引设计优化

2.3 索引设计优化 现在,我们知道了B树索引的结构特点,也了解到其对查询和排序优化的意义,但是这并不代表我们就能建好用好索引了.在实际工作中,是不是还是会遇到走了索引反而查询变慢的情况呢?虽然说不是所有的情况下索引扫描都是优于全表扫描的,但是对于一套设计成熟的系统来说,索引扫描往往是值得坚持的,应该定期进行全库SQL语句执行计划的审查,抓出全表扫描的SQL进行优化. 说一千道一万,我们创建索引就是为了使用索引,尽可能地使查询操作能够走索引.但是,很遗憾,不是我们说走索引就能走索引,还是需要取决

mysql索引提高优化order by语句用法介绍

先我们要注意一下 1>mysql一次查询只能使用一个索引.如果要对多个字段使用索引,建立复合索引. 2>在ORDER BY操作中,MySQL只有在排序条件不是一个查询条件表达式的情况下才使用索引. 关于索引一些说法 MySQL索引通常是被用于提高WHERE条件的数据行匹配或者执行联结操作时匹配其它表的数据行的搜索速度. MySQL也能利用索引来快速地执行ORDER BY和GROUP BY语句的排序和分组操作. 通过索引优化来实现MySQL的ORDER BY语句优化: 1.ORDER BY的索引