KingbaseES分区表实战:从范围分区到自动间隔分区与性能裁剪
KingbaseES分区表实战:从范围分区到自动间隔分区与性能裁剪
这事得从去年说起。有张订单表,两千多万行,业务方说查询慢。上去一看索引建了一堆,执行计划还是全表 Seq Scan,扫两千多万行那种。当时没招了,按时间做了分区,同样的查询只扫一个分区,几百毫秒出结果。那一刻我才真切感受到——大表不上分区,真的就是硬扛。
金仓 V9 原生支持分区表,语法以 Oracle 兼容模式为准。下面是一台单机 V9R1C10 上的实操记录,从建表到性能验证到维护操作,完整走一遍。

建表:按月做范围分区
按 order_date 拆,每月一个分区。语法跟 Oracle 基本一样,大小写不敏感:
CREATE TABLE orders (
order_id NUMBER,
user_id NUMBER,
order_date DATE NOT NULL,
amount NUMBER(12,2),
status VARCHAR(8)
) PARTITION BY RANGE (order_date) (
PARTITION p_2025_01 VALUES LESS THAN (DATE '2025-02-01'),
PARTITION p_2025_02 VALUES LESS THAN (DATE '2025-03-01'),
PARTITION p_2025_03 VALUES LESS THAN (DATE '2025-04-01'),
PARTITION p_2025_04 VALUES LESS THAN (DATE '2025-05-01'),
PARTITION p_2025_05 VALUES LESS THAN (DATE '2025-06-01'),
PARTITION p_2025_06 VALUES LESS THAN (DATE '2025-07-01')
);
然后灌 600 万行测试数据。这里写法有点混搭,generate_series 是 PG/金仓的,DBMS_RANDOM.VALUE 是 Oracle 兼容包,但 Oracle 模式下都能跑:
INSERT INTO orders (order_id, user_id, order_date, amount, status)
SELECT g,
MOD(g, 100000) + 1,
DATE '2025-01-01' + ((g-1) / 1000000) * INTERVAL '1' MONTH
+ MOD(g-1, 1000000) * INTERVAL '1' SECOND,
ROUND(DBMS_RANDOM.VALUE(100, 10000), 2),
CASE WHEN MOD(g,10)=0 THEN 'S' ELSE 'N' END
FROM generate_series(1, 6000000) g;
灌完一定要跑 ANALYZE orders;。我有一次忘跑,优化器选了个鬼计划,查了半天才发现是统计信息没更新。这种低级错误最浪费时间。
裁剪效果拿 EXPLAIN 说话
查 3 月数据,看执行计划:
EXPLAIN (ANALYZE, BUFFERS)
SELECT count(*), sum(amount) FROM orders
WHERE order_date >= DATE '2025-03-01' AND order_date < DATE '2025-04-01';
计划里只出现 orders_p_2025_03 一个分区,大概扫 100 万行,其余 5 个分区压根没碰。这就是分区裁剪。为了对比我专门建过一张非分区的等价表,同样查询走全表 Seq Scan 扫 600 万行——差距不是一点半点,是几十倍的量级。
索引方面,分区键上建索引默认就是本地的,每个分区各一份。但显式加 LOCAL 更稳妥,省得有些场景被建成全局:
CREATE INDEX idx_orders_date_local ON orders(order_date) LOCAL;
CREATE INDEX idx_orders_user ON orders(user_id) LOCAL;
查询带分区键的时候,优化器先裁剪分区,再在命中分区内用本地索引,IO 降一个量级。本地索引还有个好处——DETACH 或 DROP 分区时索引跟着走,不用单独清理,这点比全局索引省心太多。
间隔分区:再也不用盯着加分区了
前面那张表只覆盖到 6 月,插 7 月数据直接报错 no partition of relation found。以前都是写个定时任务提前建未来几个月的分区,定时任务这东西,你懂的,迟早会忘。金仓支持间隔分区,按步长自动建,省心:
CREATE TABLE orders_auto (
order_id NUMBER,
user_id NUMBER,
order_date DATE NOT NULL,
amount NUMBER(12,2),
status VARCHAR(8)
) PARTITION BY RANGE (order_date) INTERVAL (NUMTOYMINTERVAL(1,'MONTH')) (
PARTITION p_2025_01 VALUES LESS THAN (DATE '2025-02-01')
);
只声明一个起始分区。插任意月份的数据,金仓自动按"每月"间隔补出对应分区。验证一下:
INSERT INTO orders_auto VALUES (1, 1, DATE '2025-09-15', 99.9, 'N');
SELECT partition_name, high_value FROM user_tab_partitions
WHERE table_name='ORDERS_AUTO' ORDER BY partition_name;
结果除了 p_2025_01,多了一个系统命名的 9 月分区。生产里用这个最省心,不用再担心忘加分区导致插入失败了。
DETACH 和 ATTACH:单分区独立操作
分区最大的好处之一是能单独操作某个分区。1 月数据要归档下线,直接 DETACH 成一张独立表:
-- 剥离成分立普通表,数据跟着走
ALTER TABLE orders DETACH PARTITION p_2025_01 INTO old_2025_01;
-- 这张表就能单独备份、导出、或干脆 DROP
-- sys_dump -t old_2025_01 ... 单独备份
-- 哪天要接回来也行
ALTER TABLE orders ATTACH PARTITION p_2025_01_old
FOR VALUES FROM (DATE '2025-01-01') TO (DATE '2025-02-01');
DETACH 默认拿表级锁,瞬间完成,适合维护窗口做。在线业务怕锁的话加 CONCURRENTLY,代价是多跑一轮校验、慢一点。ATTACH 的时候 FOR VALUES 的范围必须和原分区定义严格一致,对不上直接报错。这个设计挺好,不会让你接错。
踩过的坑
分区表不是建了就一定快,坑不少。
最常见的就是裁剪没生效。某次生产上发现查询还是扫所有分区,查了半天是 WHERE 里写了 WHERE TRUNC(order_date) = DATE '2025-03-01'。函数包住分区键,裁剪直接失效。改成 WHERE order_date >= DATE '2025-03-01' AND order_date < DATE '2025-04-01' 就好了。记住一条——判断裁剪有没有生效,永远看 EXPLAIN 里实际扫了几个分区。
插入越界报错前面提过了。应用插了条 7 月的数据,直接 no partition of relation found。要么提前建好,要么直接上间隔分区。
本地索引建成全局这个坑比较隐蔽。分区键上建索引如果不加 LOCAL,有些版本会建成全局索引,维护成本高不说,裁剪的时候还用不上。加 LOCAL 两个字的事,别省。
跨分区聚合慢也碰到过。按用户汇总所有月份的订单,每个分区各算一遍再汇总,不一定比单表快多少。这种要么把分区键也设成查询维度,要么上物化视图预聚合。
ATTACH 范围不符,一般是被接回的表里有数据落在了声明范围外。接之前最好 check 一下数据范围。
最后
分区这东西解决的是大表的物理拆分。查询靠裁剪降 IO,维护靠单分区操作降风险,冷热分离靠 DETACH/ATTACH。它不是索引的替代品,分区内的索引照样要建。关键是想清楚按什么拆、查询怎么带分区键,否则建了也是白建。
那张两千多万行的表分区之后查询从几十秒降到几百毫秒。但说真的,如果一开始设计的时候就想好分区键,后面根本不用这么折腾。分区这事越早考虑越好,等表涨到几千万行再动,迁移成本就高了。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)