Skip to content

分区表:让千万级数据查询如丝般顺滑

你有没有被一张几千万行的表折磨过?

查询一个月的订单数据,要扫描全表 10 分钟。增加硬件、加索引都收效甚微。

这是因为你掉进了「单表天花板」的坑。而分区表,就是破局的关键。

分区表的本质:分而治之

分区表把一张大表拆成多个小表,查询时只扫描相关的分区,而不是全表。

普通表:orders (1亿条记录)
分区表:orders (按月分成 12 个分区,每个分区 ~800万条)

查询 2024年3月的数据:
- 普通表:扫描 1 亿条,逐一过滤
- 分区表:只扫描 2024年3月那个分区,~800万条

达梦数据库的分区类型

范围分区(Range Partitioning):按区间划分

最适合时间序列数据。

sql
-- 按订单日期分区
CREATE TABLE orders (
    order_id BIGINT PRIMARY KEY,
    order_date DATE NOT NULL,
    customer_id BIGINT,
    amount DECIMAL(10,2)
)
PARTITION BY RANGE(order_date) (
    PARTITION p2023 VALUES LESS THAN ('2024-01-01'),
    PARTITION p2024_q1 VALUES LESS THAN ('2024-04-01'),
    PARTITION p2024_q2 VALUES LESS THAN ('2024-07-01'),
    PARTITION p2024_q3 VALUES LESS THAN ('2024-10-01'),
    PARTITION p2024_q4 VALUES LESS THAN ('2025-01-01'),
    PARTITION p_future VALUES LESS THAN (MAXVALUE)
);

列表分区(List Partitioning):按枚举值划分

适合有明显类别的数据。

sql
-- 按地区分区
CREATE TABLE sales (
    sale_id BIGINT PRIMARY KEY,
    region VARCHAR(20),
    amount DECIMAL(10,2)
)
PARTITION BY LIST(region) (
    PARTITION p_east VALUES ('北京', '天津', '河北'),
    PARTITION p_west VALUES ('四川', '云南', '贵州'),
    PARTITION p_others VALUES (DEFAULT)
);

哈希分区(Hash Partitioning):均匀分布

让数据均匀分布在多个分区,适合没有明显分区键的场景。

sql
-- 按用户ID哈希分区
CREATE TABLE user_logs (
    log_id BIGINT PRIMARY KEY,
    user_id BIGINT,
    log_content VARCHAR(500)
)
PARTITION BY HASH(user_id) PARTITIONS 16;

复合分区:组合拳

范围分区 + 列表分区,或者范围分区 + 哈希分区。

sql
-- 先按年分区,再按地区列表分区
CREATE TABLE sales_report (
    id BIGINT PRIMARY KEY,
    sale_date DATE,
    region VARCHAR(20),
    amount DECIMAL(10,2)
)
PARTITION BY RANGE(sale_date)
SUBPARTITION BY LIST(region) (
    PARTITION p2024 VALUES LESS THAN ('2025-01-01') (
        SUBPARTITION p2024_east VALUES ('北京', '天津', '河北'),
        SUBPARTITION p2024_west VALUES ('四川', '云南', '贵州')
    )
);

分区表的查询优化

分区表的优势,只有在查询带上分区键时才能体现。

java
// 分区表查询示例
public class PartitionQueryDemo {

    public void queryExamples() {
        // ✅ 带上分区键:利用分区裁剪,只扫描一个分区
        String sql1 = "SELECT * FROM orders WHERE order_date = '2024-03-15'";

        // ✅ 范围查询:利用分区裁剪,扫描连续的几个分区
        String sql2 = "SELECT * FROM orders WHERE order_date BETWEEN '2024-01-01' AND '2024-03-31'";

        // ❌ 不带分区键:全表扫描,所有分区都跑不掉
        String sql3 = "SELECT * FROM orders WHERE customer_id = 12345";

        // ✅ 如果必须按非分区键查询,考虑创建本地索引
        String sql4 = "SELECT * FROM orders PARTITION (p2024_q1) WHERE customer_id = 12345";
    }
}

关键点:分区键一定要出现在 WHERE 条件里!

分区表的维护操作

添加新分区

sql
-- 为范围分区添加新分区
ALTER TABLE orders ADD PARTITION p2025_q1 VALUES LESS THAN ('2025-04-01');

删除旧分区(快速清理历史数据)

sql
-- 删除历史分区(比 DELETE 快百倍)
ALTER TABLE orders DROP PARTITION p2023;

-- 或者截断分区(保留表结构,清空数据)
ALTER TABLE orders TRUNCATE PARTITION p2023;

拆分分区

sql
-- 将一个大分区拆成两个小分区
ALTER TABLE orders SPLIT PARTITION p2024_q1 AT ('2024-02-01')
INTO (PARTITION p2024_jan, PARTITION p2024_feb);

分区索引:分区表的好搭档

分区表上可以创建全局索引本地索引

sql
-- 本地索引:索引与数据分区一一对应
CREATE INDEX idx_order_date ON orders(order_date) LOCAL;

-- 全局索引:索引不分区,全局唯一
CREATE INDEX idx_customer ON orders(customer_id) GLOBAL;

本地索引 vs 全局索引:

特性本地索引全局索引
分区独立性每个分区独立管理统一管理
维护代价分区操作时自动维护分区操作时需要重建
查询性能受限于单个分区全局范围查询更快
推荐场景范围分区、时间数据点查询、唯一约束

分区表使用注意事项

分区键选择原则:

  1. 高选择性的字段(每个分区数据量均衡)
  2. 经常作为查询条件的字段
  3. 更新频率低的字段(更新分区键代价高)

分区数量控制:

  • 分区过多会增加元数据开销
  • 建议单表分区数不超过 1000 个
  • 每个分区数据量建议在 100 万- 1000 万之间

面试追问方向

  • 分区表和分库分表有什么区别?各自适用什么场景?
  • 如果分区键的值超出预设范围会怎样?
  • 分区表的主键应该怎么设计?

一句话总结

分区表是大数据量的救世主:分区键选对了,1 亿条数据查起来像 100 万条;分区键选错了,还不如不分区。记住,查询不带分区键,等于白分。

基于 VitePress 构建