前言
随着业务数据规模不断增长,单条SQL的执行时间逐渐成为影响系统性能的重要因素。在多核CPU已经成为服务器标配的今天,如何充分利用硬件资源提升查询效率,是数据库优化过程中经常面对的问题。
KingbaseES提供了完善的并行查询能力,可以将一个大型查询拆分为多个任务,由多个Worker协同完成,从而降低单个CPU核心的计算压力,缩短SQL执行时间。
但在实际工作中,经常会遇到这样的情况:
明明表中已经有数百万甚至上千万数据,CPU资源也比较充足,执行计划却依然选择单线程执行;甚至添加了并行Hint之后,SQL仍然没有走并行计划。
本文结合实际案例,对KingbaseES中的并行机制进行分析,并介绍影响并行执行计划生成的几个关键因素。
一个简单的并行案例
构造1000万行测试数据:
sql
CREATE TABLE app_family2(
family_id varchar(32),
application_id varchar(32),
family_number varchar(50),
household_register_number varchar(50),
poverty_reason varchar(32)
);
INSERT INTO app_family2
SELECT generate_series(1,10000000),
generate_series(1,10000000),
'aaaa',
'aaa',
'bbb';
查询全部数据:
sql
EXPLAIN ANALYZE
SELECT * FROM app_family2;
执行计划如下:
text
Gather
Workers Planned: 2
Workers Launched: 2
-> Parallel Seq Scan
可以看到数据库启动了两个并行Worker参与扫描,每个Worker负责部分数据读取工作,最终由Gather节点汇总结果。
这种执行方式能够有效利用多核CPU资源,在大表扫描场景下通常能够获得较好的性能收益。
为什么有些SQL无法走并行
很多用户会发现,同样的数据量下,有些SQL能够使用并行,而有些SQL始终是单线程执行。
例如:
sql
INSERT INTO testu
SELECT COUNT(*)
FROM test;
查看执行计划:
text
Insert on testu
-> Aggregate
-> Seq Scan on test
整个执行过程只有一个Seq Scan,没有出现Gather和Parallel Seq Scan节点。
即使开启:
sql
SET force_parallel_mode=on;
执行计划仍然不会发生变化。
这是因为当前版本中,INSERT INTO ... SELECT语句不会生成并行查询计划。
对于需要利用并行能力完成数据装载的场景,可以采用以下方式改写SQL:
sql
SELECT COUNT(*)
INTO testu
FROM test;
或者:
sql
CREATE TABLE testu AS
SELECT COUNT(*)
FROM test;
改写后执行计划中即可看到:
text
Gather
Workers Planned: 2
-> Parallel Seq Scan
实现并行计算。
并行度并不是越大越好
很多DBA在看到并行功能后,第一反应是提高Worker数量。
例如:
sql
SET max_parallel_workers_per_gather=4;
再次执行查询:
text
Workers Planned: 4
Workers Launched: 4
数据库会启动4个Worker参与计算。
但需要注意的是,并行度增加并不意味着性能一定提升。
随着Worker数量增加,会产生:
- 任务分发开销
- 结果汇总开销
- 进程调度开销
当数据量较小或者CPU资源紧张时,过高的并行度反而可能导致性能下降。
因此并行度设置需要结合:
- CPU核心数量
- 数据规模
- 系统负载
综合评估,而不是简单地无限增大。
优化器为什么放弃并行
实际生产环境中,更常见的问题是:
数据库明明支持并行,却没有选择并行执行。
原因通常来自优化器成本评估。
KingbaseES在生成执行计划时,会比较:
text
串行执行成本
VS
并行执行成本
如果优化器认为:
text
并行收益 < 并行开销
则不会生成并行计划。
其中影响较大的参数是:
sql
parallel_tuple_cost
parallel_setup_cost
例如:
sql
SET parallel_tuple_cost=0.001;
重新查看执行计划:
text
Gather
Workers Planned: 2
-> Parallel Seq Scan
此时优化器会更倾向于采用并行方案。
因此,在一些大表扫描场景下,如果发现执行计划始终无法并行,可以适当调整相关成本参数进行验证。
自定义函数导致并行失效
除了参数因素外,自定义函数也是影响并行执行的重要原因。
例如:
sql
SELECT
family_id,
fun_getdistance1(
family_id::numeric,
application_id::numeric
)
FROM app_family2;
即使增加Parallel Hint,执行计划仍然可能是单线程。
原因在于函数默认并不一定支持并行执行。
可以通过以下方式查看函数属性:
sql
SELECT *
FROM sys_proc
WHERE proname='fun_getdistance1';
如果函数被标记为:
text
PARALLEL UNSAFE
则无法参与并行执行。
修改方式:
sql
ALTER FUNCTION fun_getdistance1
PARALLEL SAFE;
修改后优化器才会考虑生成并行计划。
CTE场景下的并行限制
另一个容易忽略的问题是WITH语句。
例如:
sql
WITH a AS (
SELECT *
FROM app_family
)
SELECT *
FROM a;
执行计划通常为:
text
Seq Scan on app_family
不会出现并行扫描节点。
因此在需要极致性能的场景下,应关注CTE对执行计划的影响,必要时考虑改写SQL结构。
总结
并行查询本质上是利用多核CPU资源换取更短的SQL执行时间,但并不是所有SQL都能够自动获得并行能力。
从实际案例来看,影响并行执行的因素主要包括:
- SQL类型是否支持并行
- Worker数量配置
- 优化器成本评估参数
- 自定义函数并行属性
- CTE等特殊SQL结构
在日常性能优化过程中,建议首先通过执行计划确认SQL是否真正进入并行模式,再结合业务特点调整并行参数,而不是单纯依赖Hint或者提高并行度。只有理解优化器的决策逻辑,才能真正发挥并行查询的性能优势。