本文分享自华为云社区《【华为云MySQL技术专栏】GaussDB(for MySQL)与MySQL的COUNT查询并行优化策略》,作者:GaussDB 数据库。

1.背景介绍

统计表的行数(COUNT)是客户应用和DBA运维常用的操作。MySQL虽是业界广泛使用的OLTP数据库,但大表执行COUNT操作非常耗时,原因在于:

(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

点击关注,第一时间了解华为云新鲜技术~

深度解读GaussDB(for MySQL)与MySQL的COUNT查询并行优化策略的更多相关文章

  1. 如何优化Mysql千万级快速分页,limit优化快速分页,MySQL处理千万级数据查询的优化方案

    如何优化Mysql千万级快速分页,limit优化快速分页,MySQL处理千万级数据查询的优化方案

  2. 高性能MySQL笔记 第6章 查询性能优化

    6.1 为什么查询速度会慢   查询的生命周期大致可按照顺序来看:从客户端,到服务器,然后在服务器上进行解析,生成执行计划,执行,并返回结果给客户端.其中“执行”可以认为是整个生命周期中最重要的阶段. ...

  3. MySQL存储引擎,索引及基本优化策略

    存储引擎 与Oracle, SQL Server这些数据库不同,MySQL提供了多种存储引擎.什么是存储引擎?存储引擎其实就是一套对于数据如何存储,查询,更新,建立索引等接口的实现.不同存储引擎特性有 ...

  4. 高性能MySQL(五):查询性能优化

    当向MySQL 发送一个请求的时候MySQL 到底做了什么? 1.客户端发送一条查询给服务器 2.服务器先检查查询缓存,如果命中了缓存,则立即返回存储在缓存中的结果.否则进入下一阶段 3.服务器端进行 ...

  5. mysql中百万级别分页查询性能优化

    前提条件: 1.表的唯一索引 2.百万级数据 SQL语句: select c.* FROM ( SELECT a.logid FROM tableA a where 1 = 1 <#if pho ...

  6. mysql百万级数据分页查询缓慢优化-实战

    作为后端攻城狮,在接到分页list需求的时候,内心是这样的 画面是这样的 代码大概是这样的 select count(id) from …       查出总数 select * from …. li ...

  7. MySQL百万级数据分页查询及优化

    方法1: 直接使用数据库提供的SQL语句 语句样式: MySQL中,可用如下方法: SELECT * FROM 表名称 LIMIT M,N 适应场景: 适用于数据量较少的情况(元组百/千级) 原因/缺 ...

  8. Mysql查询优化器之关于子查询的优化

    下面这些sql都含有子查询: mysql> select * from t1 where a in (select a from t2); mysql> select * from (se ...

  9. Mysql常用30种SQL查询语句优化方法

    出处:http://www.antscode.com/article/12deee70111da0c4.html 1.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使 ...

  10. MySQL 常用30种SQL查询语句优化方法

    1.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描. 2.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉 ...

随机推荐

  1. vue.js的M-V-VM思想

    MVVM 是Model-View-ViewModel 的缩写,它是一种基于前端开发的架构模式. Model 指代的就是vue对象的data属性里面的数据.这里的数据要显示到页面中. View 指代的就 ...

  2. QuickApp 快应用中 或 nodejs 中 API接品调用时 GBK转UTF8

    请求接口地址:https://doc.quickapp.cn/features/system/fetch.html?h=fetch 第一步,安装包: npm install iconv-lite as ...

  3. Flutter(二):编写第一个Flutter App

    Create New Flutter Project 在Android Studio添加上Flutter插件以后,通过File -> New -> New Flutter Project创 ...

  4. IPsecVPN 服务器一键安装脚本

    IPsec VPN 服务器一键安装脚本 使用 Linux 脚本一键快速搭建自己的 IPsec VPN 服务器.支持 IPsec/L2TP, Cisco IPsec 和 IKEv2 协议.你只需提供自己 ...

  5. Windows库链接报错

    问题回溯 今天拿到别人已经编译好的库,发现在链接的时候出现了报错 [9/9 12.7/sec] Linking CXX shared module bin\plugins\AsensingPlugin ...

  6. 7.12考试总结(NOIP模拟12)[简单的区间·简单的玄学·简单的填数]

    即使想放弃,也没法放弃最想要的东西,这就是人 前言 这次应该是和 SDFZ 一起打的第一场比赛吧. 然而我还是 FW 一个... 这次考试也有不少遗憾,主要的问题是码力不足,不敢去直面正解,思考程度不 ...

  7. 键盘中断,键盘驱动,基于Linux0.11

    键盘,咱们做计算机这一行的自然不必多说,天天与它打交道.但熟归熟,清楚键盘背后的原理吗?键盘上都标有各键的名称,表明了各键所代表的意义,但是计算机是如何知道的?组合键是怎样实现的?按下一个代表字符的键 ...

  8. mongodb基于角色的访问控制

    https://www.mongodb.com/docs/v4.4/tutorial/enable-authentication/ https://www.mongodb.com/docs/manua ...

  9. linux命令查看内存命令free -h whereis locate find查找命令

    linux命令查看内存命令free -h  whereis locate find查找命令 1.free -h root@hz-auto-eureka-test-03:/usr/local/tomca ...

  10. navicat 连接oracle 失败

    问题: 1.使用Navicat连接Oracle数据库时,报错ORA-12504: TNS:listener was not given the SERVICE_NAME in CONNECT_DATA ...