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。它不是索引的替代品,分区内的索引照样要建。关键是想清楚按什么拆、查询怎么带分区键,否则建了也是白建。

那张两千多万行的表分区之后查询从几十秒降到几百毫秒。但说真的,如果一开始设计的时候就想好分区键,后面根本不用这么折腾。分区这事越早考虑越好,等表涨到几千万行再动,迁移成本就高了。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐