---
title: PX 并行度与速度快慢的关系-OceanBase数据库使用指南
description: 了解OceanBase数据库在实际应用中关于 PX 并行度与速度快慢的关系相关的常见问题和使用技巧，帮助您快速解决 PX 并行度与速度快慢的关系的难题。
image: https://mdn.alipayobjects.com/huamei_22khvb/afts/img/A*OSPzQ6GUQF4AAAAAQHAAAAgAeiGDAQ/original
---
切换语言

- 中文站 - 简体中文
- International - English
- 日本站 - 日本語

划线反馈

# PX 并行度与速度快慢的关系

更新时间：2026-05-15 09:06

适用版本： V3.1.x、V3.2.x、V4.0.x、V4.1.x、V4.2.x 内容类型：Troubleshoot  

并行查询 `hint /*+ parallel(n)*/` 可以开启 PX，启动多个工作线程来执行查询语句，但是经过测试发现，并行度并非越高越快。本文用实验详细辩证这并行度与速度的关系。

## 适用版本

OceanBase 数据库 V3.x、V4.x 版本。

## 如何观测 PX 线程的执行统计？

同一条 SQL 执行的所有 PX 线程使用相同的 trace_id，使用 trace_id 可以从 `gv$sql_audit` 这个表来查看 PX 线程的执行统计，每一个 PX 线程会有一个单独的记录。

`gv$sql_audit` 表与 PX 线程执行相关的一些重要字段。

1. expected_worker_count：预期的线程数，即 hint 指定的。
 2. used_worker_count：实际使用了几个线程数。
 3. qc_id：如果这个 ID 为 1，则说明是一个 PX 子线程；更具体一点，trace_id 和 px sql 的相同、query_sql 为空、plan_id 为 0，则是该 px sql 的一个 PX 子线程。
 4. dfo_id：经过测试，如果这个 ID 为 1，说明是并行 DML。
 5. sqc_id：未知，但也是 PX 相关。
 6. worker_id：工作线程的 ID，如果 parallel(16)，则会有 ID 为 0～15。

如果实际线程数是 12，那么至少会查出 13 条数据，默认按照执行时间升序排序。其中前 12 条为 PX 子线程的执行记录，最后一条为父 SQL 自己的执行记录。

![1](https://obbusiness-private.oss-cn-shanghai.aliyuncs.com/doc/img/knowledge-base/database/performance/image%20%289%29.png)

如下图：worker_id 为 0～11 一共 12 个线程，最后一条 worker_id=0 的是父 SQL，子线程和父 SQL 的记录区别很明显。

![2](https://obbusiness-private.oss-cn-shanghai.aliyuncs.com/doc/img/knowledge-base/database/performance/image-1.png)

每条子线程都有自己的执行时间，多个线程是并行执行的，所以父 SQL 最终执行了多久，取决于最久的线程查询的时间。如下图：有线程 0.3s，有线程 0.66s，最终执行时间不会小于 0.66s。

![3](https://obbusiness-private.oss-cn-shanghai.aliyuncs.com/doc/img/knowledge-base/database/performance/image-2.png)

我们可以将每个线程的 execute_time 理解为每个线程的“工作量”，每个线程查询的行数其实是不一样的，可以通过执行时间来评估每个线程的查询工作量。

## 实验过程

### 实验准备

准备一个大的分区表。

```shell
CREATE TABLE `sk_px` (
  `id` int(11) DEFAULT NULL,
  `name` char(20) DEFAULT NULL,
  KEY `idx_sk_px` (`id`) BLOCK_SIZE 16384 LOCAL
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 3 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0
 partition by hash(id)
(partition p0,
partition p1,
partition p2,
partition p3,
partition p4,
partition p5,
partition p6,
partition p7,
partition p8,
partition p9,
partition p10,
partition p11,
partition p12,
partition p13,
partition p14,
partition p15)

```

这是一个简单的分区表，根据 ID 分 16 个 hash 分区，然后向表中插入数据，一共插入了 3335w 条数据。

![3](https://obbusiness-private.oss-cn-shanghai.aliyuncs.com/doc/img/knowledge-base/database/performance/image-3.png)

每个分区的数据量都是一样的，每个分区中都有 209w 条数据。

![4](https://obbusiness-private.oss-cn-shanghai.aliyuncs.com/doc/img/knowledge-base/database/performance/image-4.png)

### 实验开始

设计如下 SQL。

1. 并行度 16，查询全表所有分区。

   ```shell
   select /*+ parallel(16)*/ count(*) from sk_px

   ```
 2. 并行度 10，查询全表所有分区。

   ```shell
   select /*+ parallel(10)*/ count(*) from sk_px

   ```
 3. 并行度 10，查询表的其中一个分区。

   ```shell
   select /*+ parallel(10)*/ count(*) from test.sk_px partition(p5)

   ```
 4. 并行度 16，查询表的其中一个分区。

   ```shell
   select /*+ parallel(16)*/ count(*) from test.sk_px partition(p5)

   ```
 5. 无并行，查询表的其中一个分区。

   ```shell
   select count(*) from test.sk_px partition(p5)

   ```

### 实验结果

1. 16px，全表。总耗时 3.8s，每个线程的工作量还算均匀，都在 1s 以上。
 2. 10px，全表。总耗时 4.9s，确实比 16px 慢一点，但已经能看出一些端倪，每个线程最少都“工作”了 2.7s，而实验一的 SQL 有很多线程只“工作”了 1.2s。
 3. 10px，指定分区。总耗时 2.5s（因为行数少了确实会少一点），但是每个 PX 子线程的工作量及其不均匀。8 个线程只工作了 0.1ms，一个线程工作了 1ms，最后一个线程工作了 2.5s，一个线程撑起了整个流水线。这导致整个并行 SQL 效率骤减，直接相当于不加 PX。
 4. 16px，指定分区。总耗时 2.1s，基本没有提升，并且后边多次测试，甚至有耗时 2.9s 的结果，比 10px 还要慢。并且出现了和 10px 指定分区同样的现象，线程工作量及其不均匀，14 个线程工作了不到 0.1ms，1 个线程工作了 2.1s。
 5. 不并行，指定分区。此 SQL 作为空白对照组，以发现总耗时也为 2.1s，甚至比 16px 还要快。

### 实验结论

一个 PX 查询，多个线程之间的工作量分配是有随机性的，不能保证每个线程的查询行数都一样，这就可能会出现 14px 比 16px 还快的情况，14px 更快的原因就在于各个子线程的工作量更均匀。

## PX 并行度的分配策略

- 系统参数 `parallel_servers_target` 控制租户任一 Unit 单元（OBServer）上的 PX 线程总数。
 - parall(n) 指定了 PX 线程的总数，总的并行读受限于系统参数`parallel_servers_target`。
 - 当对分区表跨多分区访问时，每一个分区所在的 OBServer 至少会分配一个 PX 线程，即使没有指定 parall(n)。
 - 当 parall(n) 中的 n 大于分区所在的 OBServer 节点总数时，会按照每个 OBServer 上的分区个数按比例分配并行线程，总数为 n。

示例如下：

- 若指定了 parallel(8) 去查一个分区为 16 的表，这 16 个分区打散在 1-1-1 架构上，那么会按一定的策略去分配线程，总线程数是 8，可能会有一个 OBServer 只开启 2 个线程，线程开启为 2-3-3 共 8 个。
 - 若指定了 parallel(16) 去查一个分区为 16 的表，这 16 个分区打散在 1-1-1 架构上，那么会按一定的策略去分配线程，总线程数是 16，可能线程开启为 5-5-6 共 16 个。
 - 若指定了 parallel(32) 去查一个分区为 16 的表中的其中一个分区，那么就会启动 32 个线程同时查这一个分区，线程开启为 32-0-0 共 32 个。
 - 若指定了 parallel(2) 去查一个分区为 16 的表，这 16 个分区打散在 1-1-1 架构上，那么每台 OBServer 都会开启一个线程去扫描，线程开启为 1-1-1 共 3 个。也就是说最小不会小于 OBServer 数，即使不指定 PX（除非显示禁止 PX）。

Previous

[SQL 中使用 utl_raw.cast_to_raw 函数无法同时使用并行](https://www.oceanbase.com/knowledge-base/oceanbase-database-1000000000217867)

Next

[有网络传输为何还会生成本地计划？](https://www.oceanbase.com/knowledge-base/oceanbase-database-1000000001048743) ![有帮助](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*y6ocSqN8cqsAAAAAAAAAAAAAARQnAQ)![无帮助](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*BG9IQJyLHF8AAAAAAAAAAAAAARQnAQ)![反馈](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*eTWdQKCRKHwAAAAAAAAAAAAAARQnAQ)[AI](https://www.oceanbase.com/obi) 咨询热线
