概述
MySQL经过多年的发展已然成为最流行的数据库,广泛用于互联网行业,并逐步向各个传统行业渗透。之所以流行,一方面是其优秀的高并发事务处理的能力,另一方面也得益于MySQL丰富的生态。MySQL在处理OLTP场景下的短查询效果很好,但对于复杂大查询则能力有限。最直接一点就是,对于一个SQL语句,MySQL最多只能使用一个CPU核来处理,在这种场景下无法发挥主机CPU多核的能力。MySQL没有停滞不前,一直在发展,新推出的8.0.14版本第一次引入了并行查询特性,使得check table和select count(*)类型的语句性能成倍提升。
使用
通过配置参数innodb_parallel_read_threads来设置并发线程数,就能开始并行扫描功能,默认值为4,innodb_parallel_read_threads是 session 级别的变量。
set local innodb_parallel_read_threads=4;
select count(*) from sbtest.table;
设计思想
Parallel read of index 主要利用当前的多核硬件优势, 针对当前可以并行读取的逻辑例如 SELECT COUNT() 或者 CHECK TABLE, 其主要逻辑是收集数据叶子节点的 Page Number, 使用多个 worker 并行读取数据 Page, 利用不同的回调函数来处理获取后的 rows. 目前 SELET COUNT() 和 CHECK TABLE 都是同步读取
实现
row_scan_index_for_mysql()
/* 扫描索引数据 */
row_scan_index_for_mysql() |
---|
/* SELECT COUNT() */ |
------------------------------ |
–> parallel_select_count_star() |
------------------------------ |
/* CHECK TABLE */ |
------------------------ |
–> parallel_check_table() |
基本数据结构
Parallel_reader::Scan_range: 代表当前并行扫描的范围.
Parallel_reader::Config 并行扫描的 configuration.
Parallel_reader::Scan_ctx 并行扫描的上下文 (context).
Parallel_reader::Ctx 并行读取的执行上下文 (Parallel reader execution context)
Parallel_reader 并行扫描 reader
SELECT COUNT()
我们以全表扫描 SELECT COUNT() 为例, 根据源码分析 Parallel Read 的原理:
/* SELECT COUNT() 的入口函数 */
static dberr_t parallel_select_count_star(Key_reader &reader, ulint *n_rows) {
Counter::Shards n_recs;
Counter::clear(n_recs);
const buf_block_t *prev_block = nullptr;
dberr_t err =
reader.read([&](size_t id, const buf_block_t *block, const rec_t *rec,
dict_index_t *index, row_prebuilt_t *prebuilt) {
Counter::inc(n_recs, id);
/* Only check the THD state for the first thread. */
if (id == 0 && block != prev_block) {
prev_block = block;
if (trx_is_interrupted(reader.trx())) {
return (DB_INTERRUPTED);
}
}
return (DB_SUCCESS);
});
/* 统计计数 */
*n_rows = Counter::total(n_recs);
return (err);
}
Key_reader 使用 partition() 将 B+ tree 分片, 分配各个 worker 线程, InnoDB 的 B+ 树将数据存放在所有的叶子节点, 即叶子节点为 level 0, 分配策略是从 root 节点遍历, 使用 left_leaf() 从左边由上至下直到 level N 层的节点数量大于等于 worker 线程数量:
并行读取流程
启动 worker 线程, worker 线程也就是真正的读取线程,对一个切好的 sub-tree 做 scan, worker 线程分别根据被分配的 leaf page cursor 进行顺序读取.
并行读取线程会根据创建的读取对应叶子节点的 record, 并且会根据 trx->read_view 来判断可见性.