基于湖库一体架构,统一管理结构化、半结构化与非结构化等多模态数据,一个系统承载事务处理、实时分析与 AI 工作负载。
GROUP BY / DISTINCT 跳跃式去重扫描场景问题排查
更新时间:2026-08-21 03:56
问题现象
用户在对比 OceanBase 和 MySQL 的 SQL 执行效率时,发现对于无过滤条件的 GROUP BY 查询,OceanBase 的执行时间显著长于 MySQL。具体表现为 OceanBase 执行耗时约 0.8 秒,而 MySQL 只需 0.04 秒左右,尽管两环境的表数据量相同。
OceanBase 执行计划如下:
obclient> select dbms_xplan.display_cursor(0,'all')\G;
*************************** 1. row ***************************
dbms_xplan.display_cursor(0,'all'): ================================================================================================================================================
|ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)|REAL.ROWS|REAL.TIME(us)|IO TIME(us)|CPU TIME(us)|
------------------------------------------------------------------------------------------------------------------------------------------------
|0 |MERGE DISTINCT | |120 |365475 |54 |804492 |0 |698457 |
|1 |└─TABLE FULL SCAN|tbl_x1(idx_tbl_x1_5) |21653574|106216 |21653574 |804492 |0 |105363 |
================================================================================================================================================
is_index_back=false, is_global_index=false, range(MIN,MIN,MIN ; MAX,MAX,MAX)always true
Used Hint:
-------------------------------------
tbl_x1:
table_rows:21190271
physical_range_rows:21653574
logical_range_rows:21653574
output_rows:21653574
table_dop:1
查询语句为:
SELECT c11 AS label, c16 AS sonLabel
FROM tbl_x1
GROUP BY c11, c16;
表结构如下:
CREATE TABLE `tbl_x1` (
`c01` char(32) NOT NULL,
`c02` char(32) NOT NULL,
`c03` varchar(20) DEFAULT NULL,
`c04` varchar(20) DEFAULT NULL,
`c05` varchar(11) DEFAULT NULL,
`c06` varchar(11) DEFAULT NULL,
`c07` timestamp(3) NULL DEFAULT NULL,
`c08` timestamp(3) NULL DEFAULT NULL,
`c09` char(8) DEFAULT NULL,
`c10` tinyint(4) DEFAULT NULL,
`c11` char(2) DEFAULT NULL,
`c12` varchar(64) DEFAULT NULL,
`c13` mediumtext DEFAULT NULL,
`c14` varchar(256) DEFAULT NULL,
`c15` varchar(60) DEFAULT NULL,
`c16` varchar(6) DEFAULT NULL,
`c17` mediumtext DEFAULT NULL,
`c18` char(32) DEFAULT NULL,
`c19` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
`c20` char(32) DEFAULT NULL,
`c21` timestamp(3) NULL DEFAULT CURRENT_TIMESTAMP(3),
`c22` tinyint(4) DEFAULT '0',
`c23` char(32) DEFAULT NULL,
`c24` tinyint(4) DEFAULT NULL,
`c25` mediumtext DEFAULT NULL,
`c26` varchar(45) DEFAULT NULL,
`c27` varchar(45) DEFAULT NULL,
`c28` char(32) DEFAULT NULL,
`c29` int(11) DEFAULT '0',
PRIMARY KEY (`c01`),
KEY `idx_tbl_x1_1` (`c02`, `c12`) BLOCK_SIZE 16384 LOCAL,
KEY `idx_tbl_x1_2` (`c28`) BLOCK_SIZE 16384 LOCAL,
KEY `idx_tbl_x1_4` (`c21`, `c05`, `c28`) BLOCK_SIZE 16384 LOCAL,
KEY `idx_tbl_x1_5` (`c11`, `c16`) BLOCK_SIZE 16384 LOCAL,
KEY `idx_tbl_x1_6` (`c14`, `c22`) BLOCK_SIZE 16384 LOCAL,
KEY `idx_tbl_x1_7` (`c26`) BLOCK_SIZE 16384 LOCAL
) ORGANIZATION INDEX DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC
COMPRESSION='zstd_1.3.8' REPLICA_NUM=2 BLOCK_SIZE=16384
USE_BLOOM_FILTER=FALSE ENABLE_MACRO_BLOCK_BLOOM_FILTER=FALSE
TABLET_SIZE=134217728 PCTFREE=0;
MySQL 的执行计划显示使用了 Using index for group-by 优化。
问题原因
此问题是 OceanBase 当前的设计行为(By Design)。
- OceanBase 目前仅支持
GROUP BY单个普通列且不带聚合函数的场景下压至存储层。 - 对于
GROUP BY多个普通列的情况,OceanBase 不支持将其下压到存储层,在没有特殊优化的情况下,等同于对索引表进行全表扫描,从而增加了执行时间。 - 相比之下,MySQL 8.0 使用了 Loose Index Scan(跳跃式索引扫描)技术。当查询可以利用组合索引的前缀部分进行分组时,MySQL 能够通过跳跃到下一组唯一前缀的方式,只扫描每组的第一行,从而显著减少需要扫描的行数,提高查询效率。
关键信息
- 问题 SQL 的语义是获取
(c11, c16)列组合的去重集合,可以等价改写为使用DISTINCT:
SELECT DISTINCT c11 AS label, c16 AS sonLabel
FROM tbl_x1;
- 在 MySQL 中,该语句可以利用索引
idx_tbl_x1_5 (c11, c16)的有序性,通过跳跃式扫描,只取每组的首行,执行计划中Extra列会显示Using index for group-by。 - 跳跃式扫描的特点是扫描行数少,但会产生更多的随机 IO。这种优化适用于分组列基数(NDV)较小、数据分布稀疏的场景。在本案例中,用户表行数为 21,190,271,
c11的 NDV 为 12,c16的 NDV 为 10,分组数较少,非常适合跳跃式扫描优化。
问题的风险及影响
- SQL 执行效率低下,在高并发场景下可能引起系统响应延迟增加。
适用版本
所有版本。
解决方法
- 临时缓解:增加 SQL 执行的并行度,以提高查询效率。
规避方式
对于需要频繁执行 GROUP BY 多个普通列的查询,建议通过增加并行度的方式提升查询性能。