You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PostgreSQL:如何在已有数据表上无锁创建分区?当前操作引发死锁

无锁创建PostgreSQL分区表(已有数据场景)

针对已有数据的表,直接转换为分区表会产生长时间锁表甚至死锁,推荐使用**分区交换(EXCHANGE PARTITION)**的方式实现低锁迁移,核心思路是通过元数据交换替代数据拷贝,最大程度减少锁表时间。

核心步骤

1. 复制原表结构创建分区父表

确保分区父表与原表结构完全一致(包括约束、索引、触发器、默认值等):

CREATE TABLE big_table_partitioned (
    LIKE big_table INCLUDING ALL
) PARTITION BY RANGE (created_at); -- 按需选择分区策略:RANGE/LIST/HASH

INCLUDING ALL会完整复制原表的所有属性,避免后续交换时结构不匹配。

2. 创建所需分区

根据原表数据分布和业务需求,创建覆盖历史数据和未来数据的分区:

-- 历史数据分区(示例按年份划分)
CREATE TABLE big_table_2020 PARTITION OF big_table_partitioned
FOR VALUES FROM ('2020-01-01') TO ('2021-01-01');

CREATE TABLE big_table_2021 PARTITION OF big_table_partitioned
FOR VALUES FROM ('2021-01-01') TO ('2022-01-01');

CREATE TABLE big_table_2022 PARTITION OF big_table_partitioned
FOR VALUES FROM ('2022-01-01') TO ('2023-01-01');

CREATE TABLE big_table_2023 PARTITION OF big_table_partitioned
FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');

-- 未来数据分区(接收新写入数据)
CREATE TABLE big_table_future PARTITION OF big_table_partitioned
FOR VALUES FROM ('2024-01-01') TO ('MAXVALUE');

3. 转发新写入数据到分区表

在交换原表之前,创建触发器将原表的新写入操作转发到分区表,避免数据丢失:

-- 创建转发触发器函数
CREATE OR REPLACE FUNCTION forward_to_partitioned()
RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO big_table_partitioned VALUES (NEW.*);
    RETURN NULL; -- 不再写入原表
END;
$$ LANGUAGE plpgsql;

-- 给原表绑定触发器
CREATE TRIGGER trigger_forward_to_partitioned
BEFORE INSERT ON big_table
FOR EACH ROW EXECUTE FUNCTION forward_to_partitioned();

4. 交换原表与临时分区

如果原表所有数据能被一个临时分区覆盖,先创建临时分区,再通过元数据交换将原表数据转移到分区表:

-- 创建覆盖所有原数据的临时分区
CREATE TABLE big_table_temp PARTITION OF big_table_partitioned
FOR VALUES FROM ('2020-01-01') TO ('2024-01-01');

-- 执行分区交换(元数据操作,锁表时间极短)
ALTER TABLE big_table_partitioned EXCHANGE PARTITION big_table_temp WITH TABLE big_table;

这一步是原子操作,仅交换表的元数据,不会拷贝数据,因此锁表时间可以忽略不计。

5. 拆分临时分区(可选)

如果需要将临时分区拆分为更细粒度的子分区,可在交换完成后执行拆分:

-- 拆分临时分区为2020-2023的子分区
ALTER TABLE big_table_partitioned SPLIT PARTITION big_table_temp AT ('2021-01-01') INTO (PARTITION big_table_2020, PARTITION big_table_temp_remaining);

ALTER TABLE big_table_partitioned SPLIT PARTITION big_table_temp_remaining AT ('2022-01-01') INTO (PARTITION big_table_2021, PARTITION big_table_temp_remaining);

ALTER TABLE big_table_partitioned SPLIT PARTITION big_table_temp_remaining AT ('2023-01-01') INTO (PARTITION big_table_2022, PARTITION big_table_2023);

-- 删除剩余的临时分区
DROP TABLE big_table_temp_remaining;

拆分操作仅锁定被拆分的分区,不影响其他分区的正常读写。

6. 切换表名(业务无感知)

交换完成后,原表已为空,可删除原表并将分区表重命名为原表名,保证业务代码无需修改:

DROP TABLE big_table;
ALTER TABLE big_table_partitioned RENAME TO big_table;

7. 更新统计信息

最后更新分区表的统计信息,确保查询优化器能正确生成执行计划:

ANALYZE big_table;

注意事项

  • 结构一致性:分区与原表必须完全一致(包括索引、约束、触发器、列顺序等),否则交换会失败。
  • 外键处理:如果原表有外键,需先删除外键,交换完成后再重建分区表的外键。
  • 业务低峰期操作:虽然交换锁表时间极短,但建议在业务低峰期执行,避免极端情况下的短暂阻塞。
  • PostgreSQL版本:该方法适用于PostgreSQL 11及以上版本(支持声明式分区和EXCHANGE PARTITION)。

内容的提问来源于stack exchange,提问作者praveen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 18:01:05