本文首发于 GreatSQL社区 微信公众号。
GreatSQL马上正式开源了,这次又新增了两个重磅特性:InnoDB事务锁优化 以及 InnoDB引擎的并行查询优化,这两个特性是由华为鲲鹏计算团队贡献的Patch合并而来。
InnoDB并行查询优化怎么实现的?
根据B 树的特点,可以将B 树划分为若干子树,此时多个线程可以并行扫描同一张InnoDB表的不同部分。对执行计划进行多线程改造,每个子线程执行计划与MySQL原始执行计划一致,但每个子线程只需扫描表的部分数据,子线程扫描完成后再进行结果汇总。通过多线程改造,可以充分利用多核资源,提升查询性能。
优化后,在TPC-H测试中表现优异,最高可提升30倍,平均提升15倍。
该特性适用于周期性数据汇总报表之类的SAP、财务统计等业务,例如月初、月底跑批业务等。
使用限制:
- 暂不支持子查询,可想办法改造成JOIN。
- 暂时只支持ARM架构平台,X86架构平台优化也会尽快完成。
本文针对 InnoDB引擎的并行查询优化 特性进行对比测试。
1、测试环境
服务器:神州鲲泰R222,华为Hi1616 * 2(主频 2400 MHz 共64个逻辑CPU),256G内存。
操作系统:Docker 20.10.2,Docker容器下的CentOS Linux release 7.9.2009,Linux 4.15.0-29-generic。
本次测试采用TPC-H,dbgen构造测试数据参数 dbgen -vf -s 50
,导入后数据库物理大小约70G。GreatSQL关键配置:
#运行Q10测试时,需要较大临时表
temptable_max_ram = 6G
#使得本测试基于纯内存场景
innodb_buffer_pool_size=96G
#InnoDB并行查询优化
#global级别,设置并行查询的开关,bool值,on/off。默认off,关闭并行查询特性。可在线动态修改。
force_parallel_execute = ON
#global级别,设置系统中总的并行查询线程数。有效值的范围是(0, ULONG_MAX),默认值是64。
parallel_max_threads = 64
#global级别,并行执行时leader线程和worker线程使用的总内存大小上限。有效值的范围是(0, ULONG_MAX),默认值是1G
parallel_memory_limit = 32G
2、测试数据
测试过程中,注意要确保每次查询都是基于纯内存的场景,也就是确保innodb_buffer_pool_size大于数据库物理大小,并确认查询过程中没有额外的物理I/O发生。
个别SQL例如Q10在运行过程中会产生临时表(Using temporary),这时候需要加大 temptable_max_ram 选项值。该选项默认值1G,在上述测试数据量前提下,大概需要加大到4G才能hold住。如果该选项值不够的话,可能运行过程中会提示诸如 The table '/tmp/#sql57_a1_0' is full
这样的错误提示,然后退出查询,这是MySQL的BUG#99100。
InnoDB并行查询特性通过HINT语法可以很方便地使用,首先确认启用了该特性(可在线动态打开):
代码语言:javascript复制$ mysqladmin var|grep force_parallel_execute
| force_parallel_execute | ON
那么默认所有的SQL只要符合条件,即可自动采用并行查询,通过查看执行计划确认:
代码语言:javascript复制mysql> EXPLAIN SELECT ... FROM ... WHERE ...
...
Parallel execute (4 workers)
...
可以看到执行计划输出中包含 Parallel execute (4 workers)
关键字,这就表示最高可并行4个线程查询。
也可以查看树状执行计划:
代码语言:javascript复制mysql> EXPLAIN FORMAT=TREE SELECT ... FROM ... WHERE ...
...
| -> Limit: 1 row(s)
-> Sort: lineitem.l_returnflag, lineitem.l_linestatus, limit input to 1 row(s) per chunk
-> Table scan on <temporary>
-> Aggregate using temporary table
-> Parallel scan on <temporary>
-> Sort: lineitem.l_returnflag, lineitem.l_linestatus
-> Table scan on <temporary>
-> Aggregate using temporary table
-> Filter: (lineitem.l_shipdate <= <cache>((DATE'1998-12-01' - interval '88' day))) (cost=6342898.28 rows=19669815)
-> PQblock scan on lineitem (cost=6342898.28 rows=59015354)
...
可以看到执行计划中包含 PQblock scan on ...
关键字,并且注意到同一行里提示 cost=6342898.28
,这是启用并行查询的条件之一,也就是 cost 超过了 parallel_cost_threshold = 1000
设置的阈值开关。
一条SQL若不想启用并行查询,加上相应的HINT即可:
代码语言:javascript复制mysql> SELECT /* NO_PQ */ ... FROM ... WHERE ...
也可以动态调整并行线程数为最高64线程:
代码语言:javascript复制mysql> SELECT /* PQ(64) */ ... FROM ... WHERE ...
好了,直接查看结果对比数据:
TPCH | 并行扫描(默认参数)耗时(秒) | 并行扫描(参数优化后)耗时(秒) | 未优化前耗时(秒) | 并行扫描 vs 未优化前的提升 | 提高查询并行读优化后提升 |
---|---|---|---|---|---|
Q1 | 616.407015 | 43.688772 | 1396.791060 | 31.971 | 14.109 |
Q3 | 139.579648 | 24.343778 | 330.946837 | 13.595 | 5.734 |
Q5 | 343.604734 | 30.501792 | 338.576433 | 11.100 | 11.265 |
Q6 | 248.830780 | 20.128220 | 233.490352 | 11.600 | 12.362 |
Q10 | 155.077042 | 41.948881 | 263.921069 | 6.291 | 3.697 |
Q12 | 325.281718 | 24.850585 | 582.405888 | 23.436 | 13.089 |
Q19 | 17.475904 | 5.296522 | 42.447522 | 8.014 | 3.300 |
从这个测试结果简单概括几条:
- 1、平均提升约14倍,最高提升约32倍。
- 2、如果并发量更高,则优化效果更好。
- 3、Q5原始SQL性能提升不多,调整JOIN顺序后性能提升显著(从只提升28%跃升到11倍)。
GreatSQL将于近期正式开源,欢迎关注。
全文完。
Enjoy GreatSQL :)