(1) COUNT操作须要遍历表的全量数据来获取精确的行数,当表数据量较大或部分数据不在Buffer Pool时,查询操作很耗时。
(2) MySQL 8.0.14之前的版本无并行查询技能,只能串行实行SQL语句,无法利用多核技能进行加速。
(3) MySQL 8.0.14及后续版本InnoDB存储引擎支持并行扫描主键,但不支持并行扫描二级索引,在主键很大、二级索引较小的场景下,比较老版本(MySQL 5.7)串行扫描二级索引,社区版本并行扫描可能涌现性能劣化,并且不支持关闭并行扫描主键特性。

GaussDB(for MySQL)通过自研并行查询(PQ)和打算下推(NDP)特性,办理了大表COUNT慢的问题,范例场景下,比较MySQL并行扫描主键性能可提升超过80倍。
2. MySQL COUNT并行先容MySQL8.0.14版本InnoDB存储引擎支持并行扫描主键,这样可以利用并行的能力对COUNT操作进行加速,特性解释拜会图1。
图1 MySQL 8.0 InnoDB存储引擎并行扫描主键特性
2.1事理先容MySQL COUNT并行在InnoDB存储引擎层实现的框架图拜会图2。优化器决策走COUNT并行后,天生COUNT并行算子“UnqualifiedCountIterator”, 调用handler API接口“handler::ha_records”,InnoDB层在函数“Parallel_reader::parallel_read”中调度worker线程进行拆分、扫描、计数汇总。
图2 InnoDB 并行扫描调度逻辑
下面基于MySQL 8.0.14源码,先容COUNT并行在SQL引擎和InnoDB存储引擎中的实现。
2.1.1 COUNT并行在SQL引擎中的实现
(1)SQL引擎层在优化阶段判断SQL是否为大略的COUNT,记录在变量“JOIN:: select_count”中,变量的定义拜会下方代码。
/ When join->select_count is set, tables will not be optimized away. The call to records() will be delayed until the execution phase and the counting will be done on an index of Optimizer's choice. The index will be decided in find_shortest_key(), called from optimize_aggregated_query()./bool JOIN::select_count{false};
(2)SQL引擎层在天生实行操持阶段,判断变量“JOIN::select_count”的值,如果变量值为TRUE,则天生并行COUNT算子“UnqualifiedCountIterator”,用户可以通过“EXPLAIN FORMAT=TREE”或“EXPLAIN ANALYZE”命令查看实行操持,如果包含“Count rows”关键字解释 COUNT 并行生效,拜会下面的实行操持。
mysql> explain format=tree select count() from lineitem\G 1. row EXPLAIN: -> Count rows in lineitem
2.1.2 COUNT并行在InnoDB 存储引擎中的实现
(1) SQL引擎调用handler API 接口“handler::ha_records”,通报优化器选择的索引给InnoDB存储引擎,获取COUNT结果。
(2) InnoDB存储引擎只支持主键的并行扫描,函数“ha_innobase::records_from_index”忽略索引信息,逼迫选择主键进行并行扫描。
(3) InnoDB存储引擎在函数“Parallel_reader::parallel_read”中对主键索引进行初步分片,并调度 worker 线程对分片进一步拆分、扫描、计数。
(4) 我们把InnoDB中相应“handler::ha_records”接口并调度worker进行事情的的线程称为leader线程,leader线程调用堆栈信息如下:
UnqualifiedCountIterator::Read get_exact_record_count handler::ha_records ha_innobase::records_from_index ha_innobase::records row_scan_index_for_mysql row_mysql_parallel_select_count_star Parallel_reader::run Parallel_reader::parallel_read
(5) 我们把InnoDB中相应“Parallel_reader::worker”接口并进行扫描、计数事情的线程称为worker线程,worker线程的并发度可以通过参数“ innodb_parallel_read_threads”掌握,worker线程调用堆栈信息如下:
Parallel_reader::worker Parallel_reader::Ctx::traverse Parallel_reader::Ctx::traverse_recs
2.2性能提升效果
我们利用4U16G规格ECS实例,支配MySQL Community 8.0.14版本,innodb_buffer_pool_size设置为8GB。采取TPC-H测试模型,Scale Factor(Gigabytes)为20,lineitem表主键大小约17.4GB,二级索引i_l_orderkey大小约2.3GB,二级索引i_l_partkey_suppkey大小约3.3GB,表构造如下:
mysql> show create table lineitem\G 1. row Table: lineitemCreate Table: CREATE TABLE `lineitem` ( `L_ORDERKEY` bigint NOT NULL, `L_PARTKEY` int NOT NULL, `L_SUPPKEY` int NOT NULL, `L_LINENUMBER` int NOT NULL, `L_QUANTITY` decimal(15,2) NOT NULL, `L_EXTENDEDPRICE` decimal(15,2) NOT NULL, `L_DISCOUNT` decimal(15,2) NOT NULL, `L_TAX` decimal(15,2) NOT NULL, `L_RETURNFLAG` char(1) NOT NULL, `L_LINESTATUS` char(1) NOT NULL, `L_SHIPDATE` date NOT NULL, `L_COMMITDATE` date NOT NULL, `L_RECEIPTDATE` date NOT NULL, `L_SHIPINSTRUCT` char(25) NOT NULL, `L_SHIPMODE` char(10) NOT NULL, `L_COMMENT` varchar(44) NOT NULL, PRIMARY KEY (`L_ORDERKEY`,`L_LINENUMBER`), KEY `i_l_orderkey` (`L_ORDERKEY`), KEY `i_l_partkey_suppkey` (`L_PARTKEY`,`L_SUPPKEY`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
lineitem表的主键约17GB,无法全部加载到Buffer Pool中,每次COUNT实行触发的磁盘IO基本相同(约82万次)。在这个场景下,提升InnoDB并行扫描并发度(innodb_parallel_read_threads),COUNT性能可以线性提升,1并发实行韶光约585秒,2并发实行韶光约300秒,4并发实行韶光约145秒,数据拜会图3。
图3 MySQL 8.0 COUNT并行提升效果
2.3约束限定(1) 社区MySQL COUNT并行在InnoDB存储引擎实现,只支持主键的并行扫描,忽略了优化器选择的最佳索引。当一个表主键很大、二级索引较小,比较老版本(MySQL 5.7)串行扫描二级索引,社区并行无优化效果。
(2) 社区MySQL COUNT并行只支持无WHERE条件的COUNT,缘故原由在于InnoDB存储无法进行过滤打算。
(3) 当扫描主键数据量很大时,可能会淘汰Buffer Pool中的热数据,导致后续的性能颠簸。
(4) 社区MySQL COUNT并行逼迫生效,无法关闭,当碰着(1)中的性能问题时,无法回退至串行扫描二级索引。
利用2.2节相同的测试环境和测试模型,实行“SELECT COUNT() FROM lineitem” SQL语句,比拟MySQL 5.7.44版本与MySQL 8.0.14版本实行韶光,数据拜会表1。
表1 MySQL 5.7.44与8.0.14版本COUNT实行韶光比拟
在这个场景下,MySQL 8.0版本利用4并发扫描主键,但是由于扫描的数据量较大,触发大量的磁盘IO,导致性能差于MySQL 5.7串行扫描二级索引。
3. GaussDB(for MySQL) COUNT 优化针对MySQL COUNT并行存在的问题,GaussDB(for MySQL)进行了针对性优化,通过自研的并行查询(PQ)和打算下推(NDP)特性,实现了三层并行,加快COUNT实行。框架图拜会图4。
第一层并行: SQL引擎层,通过自研并行查询,利用多核打算加速;第二层并行:InnoDB存储引擎层,通过自研打算下推特性,触发批量读要求,SAL层将批量读的Page组装、打包,并发将读要求发送至分布式存储(Page Store);第三层并行:Page Store接管到读要求后,每个Page Store内部并发相应读要求,待页面扫描、过滤、聚合操作完成后,将结果返回至打算层。图4 GaussDB(for MySQL) COUNT并行优化
3.1事理先容下面先容下GaussDB(for MySQL) COUNT优化细节。
3.1.1 支持动态关闭社区MySQL COUNT并行
当碰着2.3节的性能问题时,可以通过调度参数“innodb_parallel_select_count”动态关闭或开启MySQL COUNT并行功能,利用方法如下:
mysql> SET innodb_parallel_select_count=OFF;mysql> EXPLAIN FORMAT=TREE SELECT COUNT() FROM lineitem\G 1. row EXPLAIN: -> Aggregate: count(0) -> Index scan on lineitem using i_l_orderkey (cost=12902405.32 rows=118641035)
3.1.2 GaussDB(for MySQL)并行查询特性
GaussDB(for MySQL)支持并行查询(PQ)[1],用以降落剖析型查询场景的处理韶光,知足企业级运用对查询低时延的哀求。比较社区MySQL并行查询的诸多限定,GaussDB(for MySQL)自研的并行查询支持主键、二级索引多种扫描办法,适用于大部分SELECT语句。
针对COUNT操作,可以利用PQ特性,并行扫描二级索引,提升查询性能。
用户可以通过Hint的办法开启PQ,当实行操持中涌现Parallel、Gather关键字时,解释PQ特性生效。利用方法如下:
mysql> EXPLAIN FORMAT=TREE SELECT/+ PQ() / COUNT() FROM lineitem\G 1. row EXPLAIN: -> Aggregate: count(`<temporary>`.`0`) -> Gather: 4 workers, parallel scan on lineitem -> Aggregate: count(`<temporary>`.`0`) -> Parallel index scan on lineitem using i_l_orderkey (cost=4004327.70 rows=29660259)
3.1.3 GaussDB(for MySQL)打算下推特性
打算下推(Near Data Processing)[2]是GaussDB(for MySQL)提高数据繁芜查询效率的办理方案。针对数据密集型查询,将列投影、聚合运算、条件过滤等操作从打算节点向下推送给分布式存储层的多个节点,并行实行。通过打算下推方法,提升了并行处理能力,减少网络流量和打算节点的压力,提高了查询处理实行效率。
针对COUNT操作,可以利用NDP特性,将聚合操作下推至分布式存储,减少网络流量,提升查询性能。
用户可以通过Hint的办法开启NDP,实行操持中涌现NDP 关键字时,解释此特性生效。利用方法如下:
mysql> EXPLAIN FORMAT=TREE SELECT/+ PQ() NDP_PUSHDOWN() / COUNT() FROM lineitem\G 1. row EXPLAIN: -> Aggregate: count(`<temporary>`.`0`) -> Gather: 4 workers, parallel scan on lineitem -> Aggregate: count(`<temporary>`.`0`) -> Parallel index scan on lineitem using i_l_orderkey Using pushed NDP (aggregate) (cost=4046562.45 rows=29047384)
3.2性能优化效果
利用2.2节相同的测试环境和测试模型,实行“SELECT COUNT() FROM lineitem” SQL语句,比拟GaussDB(for MySQL)开启PQ特性与开启PQ+NDP特性的实行韶光,拜会表2。
表2 GaussDB(for MySQL) COUNT操作实行韶光
从测试结果看:只开启PQ特性,并行查询并发度设置为4,磁盘IO约13万次,查询耗时约31秒;
同时开启PQ和NDP特性,并行查询并发度设置为4,NDP通过IO合并和打算下推,大幅减少了磁盘IO,查询耗时只有1.7秒,比较社区MySQL 8.0.22 实行耗时145秒,COUNT性能提升超过80倍。
图5 GaussDB(for MySQL) COUNT优化提升效果
4.总结社区MySQL 8.0引入了并行扫描主键功能,但不支持并行扫描二级索引,导致在大表或冷数据场景(表页面数据不在Buffer Pool)反而涌现劣化,GaussDB(for MySQL)通过并行查询(PQ)和打算下推(NDP)特性,办理了大表COUNT慢的问题,范例场景下比较社区并行,性能提升超过80倍,为用户供应更加极致的体验。
5.干系参考[1] 并行查询(PQ)https://support.huaweicloud.com/usermanual-gaussdbformysql/gaussdbformysql_05_0150.html
[2] 算子下推(NDP)https://support.huaweicloud.com/usermanual-gaussdbformysql/gaussdbformysql_05_0129.html
关注#华为云开拓者同盟# 点击下方,第一韶光理解华为云新鲜技能~
华为云博客_大数据博客_AI博客_云打算博客_开拓者中央-华为云