背景
Adaptive hash index (AHI) 是InnoDB中用于加速索引查找的一个结构。InnoDB本身不支持hash索引,所有的索引检索都走B树查询。AHI可以认为是“索引的索引”。当对一个页面的访问次数满足一定条件后,将这个页面的地址存在一个hash表中,下次查询可以直接访问到页面,不需要走B树查询。
问题
天下没有免费的午餐,在加速查询的同时,AHI与其他缓存结构一样,也面临维护的问题。作为一个全局结构,在更新时必然有一个全局锁操作(btr_search_latch),一个查询里面可能会对其作多次加x_lock操作。
Percona的改进
如同Buffer Pool可以用多个instance减少锁冲突,Percona也用类似的策略来处理AHI的锁问题。由于本身就是Hash结构,这个处理既自然又方便。所有的数据节点都必然属于一个索引,AHI就用索引id来作分区的key(block->index->id).计算规则为(index_id % btr_search_index_num), 这个 btr_search_index_num 就是 Percona中引入的只读参数 innodb_adaptive_hash_index_partitions。
全局的btr_search_latch则分为btr_search_index_num 份,AHI中每个block中新增一个成员指向 btr_search_latch_part[i].
淘汰流程
缓存结构必然还涉及一个淘汰流程,大致逻辑如下:
a)x_lock(block->btr_search_latch);
b)从hash表中删除这个page信息
c)x_unlock
但因为并非所有的page都在AHI中, 若所有的页面操作都作这个处理,则会导致很多额外的锁冲突(x_lock排他),因此需要一个“预判断“的流程。
简单的判断流程是
a) s_lock(block->btr_search_latch);
b) 判断block->index是否为空
c) 若为空则unlock后直接返回(说明没有在AHI中)
….
(后续流程不表)
预判流程逻辑
这个预判断流程在 btr_search_index_num =1时是没有问题的,但当btr_search_index_num>1时,由于block->btr_search_latch是可变的,也就说存在一种状态,在线程A访问这个锁的时候,线程B刚好把某个page替换掉,甚至于会将这个block用于缓存另外一个page。这样线程A就”锁错了“
由于存在这种中间状态,在Percona现在的实现上面是通过double-check,在加锁之后作了若干判断,若出现上述的情况,则重试。代码在函数(btr_search_drop_page_hash_index)中
retry:
if (btr_search_index_num > 1) {
rw_lock_t* btr_search_latch;/* FIXME: This may be optimistic implementation still. */
btr_search_latch = (rw_lock_t*)(block->btr_search_latch);
if (UNIV_LIKELY(!btr_search_latch)) {
if (block->index) {
goto retry;
}
return;
}
rw_lock_s_lock(btr_search_latch);
if (UNIV_LIKELY(btr_search_latch != block->btr_search_latch)) {
rw_lock_s_unlock(btr_search_latch);
goto retry;
}
if (UNIV_LIKELY(!block->index)) {
rw_lock_s_unlock(btr_search_latch); //(*)
goto retry;
}
index = block->index;
ut_a(btr_search_latch == btr_search_get_latch(index->id));
}
其中行(*)也是一种中间状态,表示这个block刚刚淘汰掉。因为这些状态被认为是出现在其他线程在AHI中作淘汰操作时出现的,因此多次重试可以认为是等待其他线程完成。
BUG描述
但在实现上却漏考虑了一个背景,就是虽然innodb_adaptive_hash_index_partitions是只读变量,但AHI确是可以动态开关的。当用户调用 set global innodb_adaptive_hash_index=off时,会将所有的block.index清空。虽然AHI被关闭后,不会再有“淘汰”的逻辑,但page从LRU list中被淘汰的时候,还是必须得调用btr_search_drop_page_hash_index的,上面的代码逻辑仍会走到(*)行。在这个线程中就出现了死循环,从客户端看,就是查询线程等待。
复现步骤
1、设置my.cnf中
innodb_adaptive_hash_index_partitions=8
innodb_buffer_pool_size=32M
2、souce ahi_loop.txt;
现象是客户端出现等待,即使客户端取消该查询,但仍然占用一个cpu(死循环),若多次重试,CPU IDLE会跌为0
简单修改是在关闭AHI的时候,将block->index设置为NULL的同时,加一句 block->btr_search_latch = NULL;
相关推荐
- **Insert Buffer and Adaptive Hash Index**:插入缓冲区和自适应哈希索引的状态。 - **Log**:日志信息,如重做日志的使用情况。 - **Buffer Pool and Memory**:缓冲池和内存使用情况。 - **Row Operations**:...
2. **InnoDB增强**:MySQL5.5.6引入了更多的InnoDB特性,如支持自适应哈希索引(Adaptive Hash Index)、更高效的行锁定机制以及更高的事务处理速度,增强了系统的稳定性与可靠性。 3. **分区功能增强**:对于大型...
例如,DYNAMIC和COMPRESSED行格式可以更有效地利用磁盘空间,而InnoDB的自适应哈希索引(Adaptive Hash Index)则能动态调整以改善查询性能。 2. **JSON支持**:MySQL 5.7引入了对JSON数据类型的原生支持,允许用户...
例如,InnoDB的自适应哈希索引(Adaptive Hash Index)可以自动为最常使用的查询创建哈希索引,提高查询性能。 2. MySQL分区:MySQL支持表的分区功能,通过将大表分成多个逻辑部分,可以提高查询效率和管理性能。...
- 使用资源调度器如`innodb_adaptive_hash_index`和`thread_pool_size`。 10. **云环境下的MySQL运维**: - 在AWS RDS、Google Cloud SQL或Azure Database for MySQL等云服务上管理MySQL实例。 - 利用云服务提供...
- InnoDB存储引擎是MySQL的默认引擎,其参数如innodb_locks_unsafe_for_binlog、innodb_adaptive_hash_index等对性能有很大影响。正确配置这些参数有助于减少死锁,优化索引使用,以及提高事务处理速度。 5. **...