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

背景

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

一条 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
sql 复制代码
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)
sql 复制代码
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

sql 复制代码
SELECT count(*), sum(amount)
FROM orders
WHERE cust_id = 12345 AND status = 'PAID';

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

text 复制代码
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

消耗会持续增加。

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

sql 复制代码
CREATE INDEX idx_orders_cust_status ON orders(cust_id, status);
ANALYZE orders;

text 复制代码
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

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

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

sql 复制代码
SELECT * FROM t_code WHERE id = '1000';
text 复制代码
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

sql 复制代码
SELECT count(*)
FROM orders
WHERE date_trunc('month', order_date) = DATE '2024-06-01';

有 order_date 索引也没用:

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

改成范围:

sql 复制代码
SELECT count(*)
FROM orders
WHERE order_date >= DATE '2024-06-01'
 AND order_date < DATE '2024-07-01';
text 复制代码
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
>= 且 < Bitmap Index Scan 47.2 ms

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

sql 复制代码
WHERE upper(name) = 'ADA'
WHERE amount + 0 = 100
WHERE to_char(dt,'YYYYMM') = '202406'

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

date_trunc 常过不了)。

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

  1. skew_orders 先插 1000 行
  2. 不 ANALYZE
  3. 再插 200 万行
  4. 查 status = 'PAID'
text 复制代码
Seq Scan on skew_orders
 rows=5129
 actual rows=400200
Execution Time: 355.146 ms
sql 复制代码
ANALYZE skew_orders;
text 复制代码
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 双方数据已有序,或能够利用索引有序扫描

点查:

sql 复制代码
SELECT o.order_id, o.amount, i.sku, i.qty
FROM orders o
JOIN order_items i ON i.order_id = o.order_id
WHERE o.order_id = 10001;
text 复制代码
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

一周汇总:

sql 复制代码
SELECT o.status, count(*), sum(i.qty)
FROM orders o
JOIN order_items i ON i.order_id = o.order_id
WHERE o.order_date >= DATE '2024-06-01'
 AND o.order_date < DATE '2024-06-08'
GROUP BY o.status;
text 复制代码
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

sql 复制代码
SELECT count(*)
FROM customers c
WHERE EXISTS (
 SELECT 1 FROM orders o
 WHERE o.cust_id = c.cust_id
 AND o.status = 'CANCEL'
 AND o.amount > 900
);
text 复制代码
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 = 'CANCEL'
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 治理 需要加载插件
sql 复制代码
-- enable_hint = on
SELECT /*+ IndexScan(orders idx_orders_cust_status) */ count(*)
FROM orders
WHERE cust_id = 999 AND status = 'NEW';

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 分析和调优能力。对于需要推进数据库国产化,同时又希望控制应用改造成本和技术学习成本的项目来说,具有较强的实际价值。

相关推荐
梓沂3 小时前
Oracle 11g 跨平台迁移实战:从 Windows 到 CentOS 的完整记录
windows·oracle·centos
艾莉丝努力练剑3 小时前
【MYSQL】MYSQL学习的一大重点:事务(下)- InnoDB 事务隔离性原理(MVCC 视角)
android·数据库·b树·sql·学习·mysql·面试
毛驴赶鹿11 小时前
【金仓数据库征文】KES V9性能调优实战记录:从慢SQL排查到全链路优化
数据库·sql
山峰哥20 小时前
数据库工程与SQL调优:从慢查询到秒级响应的实战之路
java·开发语言·数据库·sql·深度优先·启发式算法
刘婉晴1 天前
【Web漏洞】SQL 注入实战技巧
前端·数据库·sql
Csvn1 天前
📊 SQL 入门 Day 13:窗口函数进阶
后端·sql
AI推荐率1 天前
品牌AI可见度监测报告生成:Python + SQL + Jinja2 自动化实践
人工智能·python·sql
爱喝水的鱼丶1 天前
SAP-ABAP:调试器高级工具使用——内表分析、SQL追踪、内存检查功能实操
运维·sql·性能优化·sap·abap·经验交流
Csvn2 天前
📊 SQL 入门 Day 12: 窗口函数基础
后端·sql