ITPub博客

首页 > 数据库 > 国内数据库 > KingbaseES V9 Oracle兼容版:SQL 调优实测

KingbaseES V9 Oracle兼容版:SQL 调优实测

原创 国内数据库 作者:sjw1933 时间:2026-08-05 21:47:03 0 删除 编辑

背景

很多数据库问题都有一个共同特点:刚开始并不明显。

一条 SQL在测试环境几十毫秒,看起来没有问题;但随着业务增长,数据量从几万增长到百万、千万级别后,隐藏的问题会逐渐放大。最终用户感知到的就是页面变慢、接口超时,甚至核心业务受到影响。

遇到 SQL 性能问题时,很多人的第一反应是增加CPU、内存,或者打开并行。但在实际运维过程中,更多的问题来自 SQL本身和优化器选择:

  • 缺少合适索引,导致数据库扫描大量无关数据;

  • SQL 写法导致索引无法使用,例如对列进行函数处理;

  • 数据变化后统计信息没有及时更新,优化器无法准确判断数据分布;

  • 大范围查询场景下,连接算法选择不合理。

因此,一次完整的 SQL 优化过程应该是:

发现慢 SQL → 分析执行计划 → 找到根因 → 进行最小调整 → 验证优化效果 →固化经验。

本文基于 KingbaseES V9 Oracle 兼容版,通过 6组典型场景,记录从问题发现到优化验证的完整过程。

环境

项 值
版本 KingbaseES V009R002C014
模式 initdb -m oracle,database_mode=oracle
确认 SELECT * FROM dual;、nvl() 可用
并行 max_parallel_workers_per_gather = 0
customers(cust_id PK, cust_name, region, level_no, created_at)
orders(order_id PK, cust_id, status, amount, order_date, remark)
order_items(item_id PK, order_id, sku, qty, price)
EXPLAIN (ANALYZE, BUFFERS, TIMING) 你的SQL;

在 SQL 调优过程中,执行计划是 DBA 最重要的依据。

EXPLAIN 展示的是优化器根据统计信息推测出来的执行路径,而 EXPLAIN ANALYZE 会真正执行 SQL,并反馈实际耗时、实际返回行数以及 Buffer 使用情况。

分析执行计划时,需要重点关注:

  • Seq Scan:是否发生了不必要的大范围扫描;

  • Index Scan:索引是否真正参与访问;

  • Hash Join、Nested Loop:连接方式是否符合业务特点;

  • rows 与 actual rows:优化器估算是否准确。

很多性能问题,并不是数据库能力不足,而是优化器因为错误的信息选择了不合适的路径。

1. 缺索引:从全表扫描到精准定位

现象:一个简单查询为什么需要扫描百万数据?

在订单系统中,根据客户和订单状态查询数据是非常常见的场景。

如果这类 SQL 执行越来越慢,首先需要确认数据库是不是走了正确的访问路径。

156ms → 0.041ms

SELECT count(*), sum(amount)FROM ordersWHERE cust_id = 12345 AND status = 'PAID';

orders 只有主键,没有 (cust_id, status)。

前

Aggregate (actual time=155.990..155.992)
 -> Seq Scan on orders (actual time=155.982)
 Filter: ((cust_id = 12345) AND (status = 'PAID'))
 Rows Removed by Filter: 2000000
 Buffers: shared hit=64 read=15936
Execution Time: 156.023 ms

从执行计划可以看到,数据库为了找到少量目标订单,不得不读取整个 orders 表。

这种情况在数据量较小时并不明显,但随着订单规模增长,全表扫描带来的 IO 消耗会持续增加。

优化的核心思路不是让数据库”更努力地扫描”,而是通过合理索引,让数据库尽可能少访问无关数据。

CREATE INDEX idx_orders_cust_status ON orders(cust_id, status);
ANALYZE orders;

后

Aggregate (actual time=0.019..0.020)
 -> Index Scan using idx_orders_cust_status on orders (actual time=0.015)
 Index Cond: ((cust_id = 12345) AND (status = 'PAID'))
 Buffers: shared hit=1 read=2
Execution Time: 0.041 ms
指标 优化前 优化后
时间 156.0 ms 0.041 ms
扫描方式 Seq Scan Index Scan
Buffer read≈15936 hit=1,read=2

等值过滤列建联合索引,选择率高的列靠前。建完立刻 ANALYZE。上线前用 EXPLAIN 看 Index Cond。

2. 隐式类型转换:一个引号导致索引失效

现象:明明有主键,为什么还是慢?

很多业务系统通过 ORM 框架访问数据库,参数类型由程序自动绑定。

看似简单的字符串字段,如果传入了数字类型,数据库可能会进行隐式转换,从而改变索引访问方式。

62.5ms → 0.021ms

CREATE TABLE t_code(id VARCHAR(32) PRIMARY KEY, name text);-- 100 万行
SELECT * FROM t_code WHERE id = 1000;
Parallel Seq Scan on t_code
 Filter: ((id)::integer = 1000)
Execution Time: 62.474 ms

数据库把 id 转成 integer,主键索引用不上。

SELECT * FROM t_code WHERE id = '1000';
Index Scan using t_code_pkey on t_code
 Index Cond: ((id)::text = '1000'::text)
Execution Time: 0.021 ms
写法 执行计划 时间
id = 1000 Parallel Seq Scan 62.5 ms
id = ‘1000’ Index Scan 0.021 ms

字符列传字符,数值列传数值。ORM 绑参最容易错。

3. 函数包列:不要让索引失去发挥空间

现象:有索引,为什么优化器还是不用?

很多开发人员习惯直接在查询条件中处理字段,例如日期格式转换、大小写转换。

这种写法表达清晰,但可能让普通 BTree 索引无法直接匹配。

307ms → 47ms

SELECT count(*)FROM ordersWHERE date_trunc('month', order_date) = DATE '2024-06-01';

有 order_date 索引也没用:

Seq Scan on orders
 Filter: (date_trunc('month', order_date) = '2024-06-01')
 Rows Removed by Filter: 1914290
Execution Time: 307.517 ms

改成范围:

SELECT count(*)FROM ordersWHERE order_date >= DATE '2024-06-01'
 AND order_date < DATE &#x27;2024-07-01&#x27;;
Bitmap Index Scan on idx_orders_date
 Index Cond: (order_date >= ... AND order_date < ...)
Execution Time: 47.213 ms
写法 执行计划 时间
date_trunc(列) = 常量 Seq Scan 307.5 ms
>= 且 <1 Bitmap Index Scan 47.2 ms

列上套函数,BTree 用不上。同类:

WHERE upper(name) = &#x27;ADA&#x27;WHERE amount + 0 = 100WHERE to_char(dt,&#x27;YYYYMM&#x27;) = &#x27;202406&#x27;

先改 SQL。业务非要表达式,再考虑表达式索引(函数须 IMMUTABLE;带时区的 date_trunc 常过不了)。

4. 统计过期:估 5 千,实 40 万

  1. skew_orders 先插 1000 行

  2. 不 ANALYZE

  3. 再插 200 万行

  4. 查 status = ‘PAID’

Seq Scan on skew_orders
 rows=5129
 actual rows=400200
Execution Time: 355.146 ms
ANALYZE skew_orders;
Seq Scan on skew_orders
 rows=396932
 actual rows=400200
Execution Time: 161.686 ms

过滤比例约 20%,继续 Seq Scan 合理。基数从 5129 回到 396932。多表连接时,这个偏差会改 NestedLoop 和 HashJoin 的选择。

大批量导入、删除、更新后,对关键表跑 ANALYZE。别默认 autovacuum 立刻跟上。读计划时固定对比 rows 和 actual rows。

5. 连接:点查 NestedLoop,报表 HashJoin

算法 适用场景
Nested Loop 外表结果集较小,内表可以通过索引点查
Hash Join 等值连接,参与连接的数据量较大
Merge Join 双方数据已有序,或能够利用索引有序扫描

点查:

SELECT o.order_id, o.amount, i.sku, i.qtyFROM orders oJOIN order_items i ON i.order_id = o.order_idWHERE o.order_id = 10001;
Nested Loop (actual time=0.046..0.058)
 -> Index Scan on orders (order_id = 10001)
 -> Index Scan on order_items (order_id = 10001)
Execution Time: 0.076 ms

一周汇总:

SELECT o.status, count(*), sum(i.qty)FROM orders oJOIN order_items i ON i.order_id = o.order_idWHERE o.order_date >= DATE &#x27;2024-06-01&#x27;
 AND o.order_date < DATE &#x27;2024-06-08&#x27;GROUP BY o.status;
HashAggregate
 -> Hash Join
 Hash Cond: (i.order_id = o.order_id)
 -> Seq Scan on order_items
 -> Hash
 -> Bitmap Heap Scan on orders
Execution Time: 923.385 ms

约 2 万订单建 Hash,再探测 500 万明细。

可继续做:过滤列有索引、投影收窄、HINT 试 NestedLoop、大报表上分区或预聚合。

6. EXISTS → Hash Semi Join

SELECT count(*)FROM customers cWHERE EXISTS ( SELECT 1 FROM orders o WHERE o.cust_id = c.cust_id AND o.status = &#x27;CANCEL&#x27;
 AND o.amount > 900);
Aggregate
 -> Hash Semi Join
 Hash Cond: (c.cust_id = o.cust_id)
 -> Seq Scan on customers
 -> Hash
 -> Seq Scan on orders
 Filter: amount > 900 AND status = &#x27;CANCEL&#x27;
Execution Time: 335.900 ms

相关 EXISTS 变成 Semi Join。内层仍全表扫,整体还是 300ms+。

建 orders(cust_id, status, amount),让内表尽量索引过滤,再看计划是否变成 Nested Loop Semi Join。

汇总表

优先级 手段 适用场景 本文结果
P0 联合索引 高频等值、范围查询 156 ms → 0.041 ms
P0 改写 SQL 列上函数、隐式类型转换 307 ms → 47 ms
P0 ANALYZE 大批量 DML 后 估算基数 5 千 → 约 40 万
P1 work_mem Hash、Sort 出现磁盘溢写 本组 5 千分组差异较小
P1 HINT 执行计划明显不合理 已验证
P2 分区、物化、并行 大表报表查询 本文未展开
P2 sys_sqltune 批量 SQL 治理 需要加载插件
-- enable_hint = onSELECT /*+ IndexScan(orders idx_orders_cust_status) */ count(*)FROM ordersWHERE cust_id = 999 AND status = &#x27;NEW&#x27;;

HINT 只止血。根因落在索引、统计、SQL 上。

排障

  1. 锁定 SQL:RT、CPU、IO

  2. EXPLAIN (ANALYZE, BUFFERS),对比 rows 与 actual

  3. 三类:缺索引、统计旧、SQL 不可索引

  4. 最小变更:先 SQL/索引,后参数/HINT

  5. 同数据复测时间与 buffers

  6. 固化:索引列序、禁止 函数(列)、批量后 ANALYZE


结果

测试项目 结果
缺索引点查 156 ms → 0.041 ms
隐式类型转换 62.5 ms → 0.021 ms
函数包列 307 ms → 47 ms
统计信息过期 估算 5,129 行,实际 400,200 行
点查 Join Nested Loop,0.076 ms
一周报表 Join Hash Join,923 ms
EXISTS Hash Semi Join,336 ms

6 组 SQL 做成回归脚本。升级、改参、大批量导入后各跑一遍。

总结

通过本次测试可以看到,KingbaseES V9 Oracle 兼容版不仅能够兼容常见的 Oracle 语法和函数,在 SQL 执行计划分析、索引使用、统计信息维护以及连接方式选择等方面,也延续了传统关系型数据库较为成熟的优化思路。

从实际使用体验来看,Oracle 技术人员迁移到 KingbaseES 后,很多既有经验仍然可以继续发挥作用。例如:

  • 可以使用 dual、nvl 等常见 Oracle 语法和函数;

  • 可以通过 EXPLAIN ANALYZE 分析 SQL 的真实执行情况;

  • 可以结合索引、统计信息和连接算法定位性能问题;

  • 可以通过 HINT 对执行计划进行干预和验证;

  • Oracle DBA 熟悉的 SQL 调优思路,在 KingbaseES 中依然具有较高的参考价值。

这意味着,对于已经长期使用 Oracle 的企业来说,KingbaseES 并不是一套完全陌生的数据库体系。无论是开发人员进行 SQL 适配,还是 DBA 开展性能分析,都能够在原有经验基础上较快完成技术迁移。

当然,兼容并不代表所有 SQL 都可以不经验证直接迁移。不同数据库在优化器实现、数据类型处理、函数特性以及执行计划细节上仍然可能存在差异。因此,在实际迁移和上线过程中,仍然需要结合具体版本、业务模型和数据规模进行充分测试。

总体来看,KingbaseES V9 Oracle 兼容版在降低 Oracle 应用迁移门槛方面表现较为成熟。它既保留了较多 Oracle 用户熟悉的使用习惯,也提供了较完整的 SQL 分析和调优能力。对于需要推进数据库国产化,同时又希望控制应用改造成本和技术学习成本的项目来说,具有较强的实际价值。

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/23825935/viewspace-3178298/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
11年数据库行业经验,Oracle ACE、PG ACE、OCM、IBM Certified DBA、MySQL OCP、PolarDB技术专家、KCP、PCTP、PCSD、达梦师资认证、PolarDB开源社区技术顾问、ITPUB认证专家、某高校外聘教师

注册时间:2019-05-05

  • 博文量
    295
  • 访问量
    300690