天天看点

MySQL · 8.0版本更新 · 性能优化篇

本文主要总结下mysql在8.0版本和性能相关的一些改动,随着新的小版本的发布,本文将不断进行更新,直到正式ga。

| <code>mysql 8.0.0</code>

在wl#9387中,在parse undo log时,通过table_id进行分组存储,在分发时确保同一个table id的记录被分配给同一个线程。(参考函数 <code>trx_purge_attach_undo_recs</code>)

当然这也意味着合理的不会产生冲突的单表操作, 无法利用到多线程purge了,也算是一个弊端。

name

desc

<code>buf_pool_t::lru_list_mutex</code>

用于保护lru链表,例如从lru链表上刷脏或驱逐page

<code>buf_pool_t::free_list_mutex</code>

保护free list及withdraw list(online resize)

<code>buf_pool_t::zip_free_mutex</code>

保护zip_free数组,该数组用于维护对压缩表产生的非标准page size的内存维护, ref buf/buf0buddy.cc

<code>buf_pool_t::zip_hash_mutex</code>

保护zip_hash, 其中存储压缩页block

<code>buf_pool_t::flush_state_mutex</code>

保护init_flush, n_flush, no_flush等数组

分配空闲block(<code>buf_lru_get_free_block</code>):

从free list获取: buf_pool_t::free_list_mutex

从unzip_lru/lru上驱逐一个空闲page,需要buf_pool_t::lru_list_mutex

批量扫描lru(<code>buf_do_lru_batch</code>): buf_pool_t::lru_list_mutex

批量扫描flush_list(<code>buf_do_flush_list_batch</code>): buf_pool_t::flush_list_mutex

脏页加入到flush_list(<code>buf_flush_insert_into_flush_list</code>): buf_pool_t::flush_list_mutex

脏页写回磁盘后,从flush list上移除(<code>buf_flush_write_complete</code>): buf_pool_t::flush_state_mutex/flush_list_mutex

从lru上驱逐page(<code>buf_lru_free_page</code>):buf_pool_t::lru_list_mutex, 及buf_pool_t::free_list_mutex(<code>buf_lru_block_free_non_file_page</code>)

<code>buf_flush_lru_list_batch</code> 使用mutex_enter_nowait 来获取block锁,如果获取失败,说明正被其他session占用,忽略该block.

有些变量的修改从通过buf_pool_t::mutex保护,修改成通过memory barrior来保护(<code>os_rmb</code> or <code>os_wmb</code>), 例如下面几个函数中均有体现:

通过对锁的拆分,降低了全局大锁的竞争,提升了buffer pool的扩展性,这个特性其实在percona server中很多年前就有了, 但直到mysql8.0版本才合并进来。

主要是用于为优化器提供更准确的信息,即数据是存在与磁盘还是内存中, 这样优化器可以更准确的做出代价计算。

增加一个全局对象(<code>buf_stat_per_index_t</code>)来管理所有的索引页计数

为了避免引入新的全局锁开销,实现并使用一个lock-free的hash结构(<code>"include/ut0lock_free_hash.h</code>)来存储索引信息,key值为索引id。(目前索引id具有唯一性,但不排除未来可能发生改变)。

增加计数:

page刚从磁盘读入内存 (<code>buf_page_io_complete --&gt; buf_page_monitor</code>)

创建一个新的page (<code>btr_page_create</code>)

递减计数: page从lru上释放时进行递减(<code>buf_lru_block_remove_hashed</code>)

增加新的information_schema.innodb_cached_indexs 打印每个索引在内存中的page个数,其结构如下:

为了减少对btree的锁占用,innodb在读取数据时实际上是有一个小的缓存buffer。对于连续记录扫描,innodb在满足比较严格的条件时采用row cache的方式连续读取8条记录(并将记录格式转换成mysql format),存储在线程私有的<code>row_prebuilt_t::fetch_cache</code>中;这样一次寻路就可以获取多条记录,在server层处理完一条记录后,可以直接从cache中取数据而无需再次寻路,直到cache中数据取完,再进行下一轮。

在wl#7093中引入了新的接口,由于优化器可以估算可能读取的行数,因此可以提供给存储引擎一个更合适大小的row buffer来存储需要的数据。大批量的连续数据扫描的性能将受益于更大的record buffer。

record buffer由优化器来自动决定是否开启,增加新的类<code>record_buffer</code>进行管理, record buffer的大小最大不超过128kb, 目前是hard code的,不可以配置。

判断及分配record buffer函数: <code>set_record_buffer</code>, 并通过新的api接口(<code>handler::ha_set_record_buffer</code>)传到引擎层

buffer本身是引擎无关的,在sever层分配,通过handler成员m_record_buffer传递到引擎层。

增加新的接口,判断是否支持record buffer, 目前仅innodb支持,需要满足如下条件 (ref <code>set_record_buffer</code>):

access type 不是 ref, ref_or_null, index_merge, range, index 或者all

不是临时表

不是loose index scan

进入innodb引擎层判断(<code>(</code>row_prebuilt_t::can_prefetch_records<code>)</code>)

在innodb中,当record buffer被配置时,就使用server层提供的record buffer,而不是<code>row_prebuilt_t::fetch_cache</code>

该worklog的目的是改进短连接场景下的性能,对thd list的操作可能导致比较高的锁竞争。

解决方案也比较传统,就是进行分区,将链表thd_list划分成多个数组,目前为8个分区,相应的锁<code>lock_thd_remove</code>和<code>lock_thd_list</code>锁也进行了分区。