前言

随着业务数据规模不断增长,单条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或者提高并行度。只有理解优化器的决策逻辑,才能真正发挥并行查询的性能优势。