概述

分区的本质是将一张大的物理表从逻辑上拆分,为 N 个较小的物理表。

分区表按照官方的解释如下:

The partitioned table itself is a “virtual” table having no storage of its own. Instead, the storage belongs to partitions, which are otherwise-ordinary tables associated with the partitioned table. Each partition stores a subset of the data as defined by its partition bounds. All rows inserted into a partitioned table will be routed to the appropriate one of the partitions based on the values of the partition key column(s). Updating the partition key of a row will cause it to be moved into a different partition if it no longer satisfies the partition bounds of its original partition.

分区表本体作为 「虚拟表」 ​存在,自身不持有实际存储空间。其物理存储由关联的分区(即普通物理表,子表)承担,每个分区通过预定义的分区边界(Partition Bounds)存储对应的数据子集。所有插入操作将依据分区键列(Partition Key)的值自动路由到目标分区。若更新某行的分区键值导致其超出原分区的边界,该行将被迁移至新的分区。

按照上面的解释,我们可以得出以下的一些结论:

  • 数据存储:分区表的主表是一张逻辑表(虚拟表),它不负责存储数据,只负责数据的分发,所有的数据都是存储在子表中。因此,主表的数据操作是不会产生 WAL 日志,他的 WAL 日志会由基础的子表产生。所以,我们的数据实时同步,应该监听的是子表,才能获取到 WAL 日志。

  • 数据分发:当对主表的任何一个操作,PG 会经过处理转化下发到指定的子表。

    • 数据插入&删除:根据分区键的分区策略,将操作数据自动路由到指定子分区。

    • 数据更新:

      • 路由查找:数据更新的 WHERE ​语句中包含分区键,则会自动到路由到指定的子表。如果没有,则会将此 UPDATE ​路由到所有的分区,找到待更新的数据。

      • 数据更新中带有分区字段:假如数据更新中,更新了分区字段,且此分区字段的值改变了此条数据的所属分区,则会执行两个操作:

        • 现所属分区删除数据:因为此数据已不属于该分区,所以此条更新的数据将从此分区删除。
        • 新分区插入数据:将 UPDATE ​后的数据 INSERT ​到新分区。
    • 数据查询:

      • 查询条件中带有分区字段且能够定位到指定的一个分区:直接查询该分区的数据,然后返回。
      • 查询条件中无分区字段或分区字段的值只能定位到一个模糊的分区范围:查询定位到的 N 个分区,然后再把查询出来的数据,进行二次处理,返回。
  • DDL 操作:在父表上面的任何 DDL 操作,都会经过处理,分配到每个子表上面。

分区方式

PostgreSQL 提供了以下的分区方式

范围分区(Range Partitioning)

基于分区键列(单列或多列)划分连续且互斥的数值区间。例如按日期范围(如 2023-Q1)或业务 ID 区间划分。边界规则:包含下限值,不包含上限值(即左闭右开)。

示例:分区 A 范围[1,10),分区 B 范围[10,20),数值 10 归属分区 B。

列表分区 (List Partitioning)

通过显式枚举分区键值定义分区。每个分区存储指定的离散值集合。

示例:按地区分区,华东分区包含('上海','江苏','浙江')。

哈希分区 (Hash Partitioning)

通过取模运算分配数据:指定模数(modulus)和余数(remainder),分区键哈希值取模后匹配余数的行存入对应分区。

示例:模数=4,余数=0 的分区存储哈希值 mod 4 = 0 的数据行。

对比

维度 范围分区 (Range) 列表分区 (List) 哈希分区 (Hash)
分区逻辑 连续区间(数值/日期等) 离散值枚举(地区/状态等) 哈希取模运算
边界定义 FROM A TO B(左闭右开) IN (v1, v2...) WITH (MODULUS N, REMAINDER M)
数据分布 可能不均匀(如历史数据集中) 人工指定,灵活但需预定义 强制均匀分布
查询优化 ️ 高效支持范围查询
️ 分区剪枝优化
️ 精准匹配查询快
️ 等值查询优化
️ 等值查询快
️ 并行扫描均衡
典型场景 时间序列(日志、销售记录) 业务分类(地区、产品线) 分布式存储(用户 ID、随机键)
边界管理 需防区间重叠 需防值重复 余数需覆盖 0 到(modulus-1)
缩容成本 高(需重组相邻分区) 中(修改枚举列表) 极高(需重分布所有数据)
扩容成本 低(增加新分区即可) 低(增加新分区即可) 极高(需重分布所有数据)
子分区支持 多级分区(如年 → 月) 多级分区(如国家 → 城市) ️ 仅单层

优缺点

优点

  1. 查询性能优化

    • 分区剪枝:自动跳过无关分区(如 WHERE date > '2023-01-01' 仅扫描新分区)
    • 局部索引:高频分区索引常驻内存,减少 I/O
    • 并行扫描:不同分区可由多个 Worker 同时读取
  2. 数据管理高效

    • 秒级删除旧数据DROP TABLE partition_2020DELETE 快 1000 倍以上
    • 零碎片化:避免 DELETE 导致的表膨胀和 VACUUM 压力
  3. 运维灵活性

    • 滚动维护:分区级 VACUUM 不锁全表
    • 动态挂载ATTACH/DETACH PARTITION 实现数据秒级切换
    • 避免出现超级大表:超级大表的维护会异常的困难(例如添加索引、字段和修复数据等操作),消耗的性能和花费是时间都会让表的维护异常的困难!

缺点

  1. 设计复杂性

    • 需要合理的分区设计:需要合理的选择分区方案,假如分区方案选择不合理,会加大系统的负载和分区管理的复杂,导致运维起来更为复杂。
  2. 功能限制

    • 全局约束受限:唯一索引必须包含所有分区键
    • 跨分区事务缺失:不支持分布式 ACID(如跨分区行级锁)
    • 子分区扩展列禁止:所有分区必须与父表列完全一致
  3. 性能陷阱

    • 分区键更新代价高:触发行迁移(等效 DELETE + INSERT
    • 规划器超时风险:超过 1000 个分区时查询计划生成延迟显著增加
    • 元数据内存膨胀:每个会话缓存分区树,消耗额外 RAM
  4. 运维成本

    • 统计信息收集繁琐:需对每个分区单独 ANALYZE
    • 工具链兼容性差:部分 ORM/备份工具无法正确处理分区表
    • 版本升级风险:PG 10-13 的分区管理性能远低于 PG 14+
  5. 对开发要求更高:

    • 合理使用分区特性门槛较高: 分区表的高效查询插入需要指定条件才能触发,如果使用不当,反而会加大数据库的负载!
    • 分区表日常维护更为复杂: 分区表的索引、字段和分区的维护比单表更为复杂,需要详细了解才能避免各种风险!

分区操作

下面以这张 parcel ​表来示例,我们是如何合理的进行分区操作:

CREATE TABLE parcel
(
id INTEGER DEFAULT NEXTVAL('parcel_id_seq'::REGCLASS) NOT NULL,
tracking_number VARCHAR,
created_at TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP,
transporter VARCHAR(255),
platform VARCHAR(50),
failed_count INTEGER DEFAULT 0,
server_name VARCHAR(255),
archived BOOLEAN DEFAULT FALSE NOT NULL,
PRIMARY KEY (id)
);

分区规则

首先,我们的分区规则如下:

一级分区主要根据 archived 字段分区:

  • archived=FALSE : 则数据保留在 ord_parcel_hot 子表中
  • archived=TRUE : 则数据保留在 ord_parcel_history 子表中

二级分区是再根据 created_at 等时间字段,在 ord_parcel_history 的基础上再进行划分分区

  • archived=TRUE & created_at = '2025-07-21 00:00:00' :数据表留在 ord_parcel_history_2025 分区
  • archived=TRUE & created_at = '2024-07-21 00:00:00' :数据表留在 ord_parcel_history_2024 分区
  • ...
  1. 简单来说,archived 控制是否在 hot 表,还是在 history 表,created_at 控制在那张 history 表
  2. history 分区范围不一定要是按照年分区,假如数据量比较大,则也可以改为半年或季度分区,这个主要取决于数据量大小,建议单个 history 分区的数据量 < 5000w

分区表的结构如下:

--| parcel
└--|parcel_hot
└--|parcel_history
└--|parcel_2025
└--|parcel_2024
└--|parcel_2023
└--|parcel_xxxx
└--|parcel_before

创建分区表

创建 parcel ​主表

要点:

  • 主键为 (id, archived, created_at)

  • 分区方式和分区键:PARTITION BY LIST (archived)

    • 分区方式为 LIST 分区
    • 分区键为 archived 字段
CREATE TABLE parcel
(
id INTEGER DEFAULT NEXTVAL('parcel_id_seq'::REGCLASS) NOT NULL,
tracking_number VARCHAR,
created_at TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP,
transporter VARCHAR(255),
platform VARCHAR(50),
failed_count INTEGER DEFAULT 0,
server_name VARCHAR(255),
archived BOOLEAN DEFAULT FALSE NOT NULL,
PRIMARY KEY (id, archived, created_at)
)
PARTITION BY LIST (archived);

创建 parcel_hot 数据表挂载在 parcel ​数据表下面

-- 为 parcel 添加分区:
-- 当 archived = FALSE,则分配至 hot 表
CREATE TABLE parcel_hot PARTITION OF parcel
FOR VALUES IN (FALSE);

创建二级分区的主表 parcel_history,挂载在 parcel ​数据表下面

-- 当 archived = TRUE,则分配至 history 表,且此表再根据 created_at 的 RANGE 分区方式,再进行分区
CREATE TABLE parcel_history PARTITION OF parcel
FOR VALUES IN (TRUE)
PARTITION BY RANGE (created_at);

创建 parcel_history ​下面的子表,均挂载到 parcel_history ​二级分区的主表下

-- 添加 clr_parcel_clearance_history_2025  至 clr_parcel_clearance_history
CREATE TABLE parcel_history_2025 PARTITION OF parcel_history
FOR VALUES FROM ('2025-01-01') TO ('2026-01-01'); CREATE TABLE parcel_history_2024 PARTITION OF parcel_history
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01'); CREATE TABLE parcel_history_2023 PARTITION OF parcel_history
FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');

以上,就完成了一张分区表的创建!

索引维护

官方文档:

As mentioned earlier, it is possible to create indexes on partitioned tables so that they are applied automatically to the entire hierarchy. This can be very convenient as not only will all existing partitions be indexed, but any future partitions will be as well. However, one limitation when creating new indexes on partitioned tables is that it is not possible to use the CONCURRENTLY qualifier, which could lead to long lock times. To avoid this, you can use CREATE INDEX ON ONLY ​the partitioned table, which creates the new index marked as invalid, preventing automatic application to existing partitions. Instead, indexes can then be created individually on each partition using CONCURRENTLY and attached to the partitioned index on the parent using ALTER INDEX ... ATTACH PARTITION. Once indexes for all the partitions are attached to the parent index, the parent index will be marked valid automatically.

如前所述,在分区表上创建索引时可使其自动应用于整个分区层次结构。这种方式非常便捷——不仅所有现有分区会建立索引,未来新增的分区也将自动同步创建。但需要注意,分区表创建新索引时存在一项限制:无法使用 CONCURRENTLY ​修饰符,这可能导致长时间锁定表。

为避免此问题,可采用 CREATE INDEX ... ONLY ​语法在分区表上创建索引,此时新建索引会被标记为无效状态,且不会自动应用到现有分区。随后可执行以下操作:

  1. 在每个分区上使用 CONCURRENTLY ​分别创建索引
  2. 通过 ALTER INDEX ... ATTACH PARTITION 将分区索引挂载至父表的索引

当所有分区索引都完成挂载后,父级索引将自动标记为生效状态。

由上面的官方文档我们可以得知:

  • 分区表的父表是虚拟表,所以它的索引也是虚拟索引,当操作父表的索引的时候,它会在所有的子表上面,都创建和父表等效的索引。

  • 创建主表的索引,无法使用 CONCURRENTLY ​关键字,这意味着在主表上面操作索引,会进行长时间的锁表。

    • 官方建议使用 CREATE INDEX ... ONLY 解决锁表问题

因此,我们创建索引有两种方式:父表创建索引和子表创建索引,两种创建索引的对比:

特性 父表(Partitioned Table)创建索引 子表(Partition)创建索引
索引定义方式 CREATE INDEX idx_parent ON parent_table (key);
(自动级联到所有子表)
需在每个子表单独创建:
CREATE INDEX idx_child1 ON child1 (key);
索引物理存储 虚拟索引(无实际数据),实际数据在各子表的本地索引 独立的物理索引
查询优化器行为 自动识别分区剪枝,仅扫描相关分区的本地索引 需手动确保所有子表有索引,否则未索引分区全表扫描
新增分区支持 自动为新分区创建索引 需手动为新分区创建索引
索引类型限制 不支持表达式索引/部分索引(需在子表单独创建) 支持任意索引类型
唯一约束实现 必须包含分区键(全局唯一性难保障) 可创建子表局部唯一索引(但无法跨分区唯一)

索引添加

因此,根据以上的信息,假如我们需要在数据表上面添加索引,按照下面的例子:

本次我们目前需要在 parcel_history ​上面添加 tracking_number ​索引。

使用 CREATE INDEX ... ONLY ​关键字在主表 parcel_history ​上面添加索引:

-- 注意添加 ONLY 关键字
CREATE INDEX idx_parcel_history_tracking_number
ON ONLY parcel_history (tracking_number);

使用此 SQL ​查询当前父表 parcel_history ​索引是否标记为有效:

-- 当前索引状态应该返回 FALSE
SELECT
c.relname AS index_name,
i.indisvalid AS is_valid
FROM pg_index i
JOIN pg_class c ON c.oid = i.indexrelid
WHERE c.relname = 'idx_parcel_history_tracking_number';

在对应子表上面使用 CONCURRENTLY ​关键字添加索引,避免锁表操作:


CREATE INDEX CONCURRENTLY idx_parcel_history_2025_tracking_number
ON parcel_history_2025 (tracking_number); CREATE INDEX CONCURRENTLY idx_parcel_history_2024_tracking_number
ON parcel_history_2024 (tracking_number); -- ...

将新加的索引,通过 ATTACH PARTITION ​操作,添加到 parcel_history ​表 idx_parcel_history_tracking_number ​上面:

ALTER INDEX idx_parcel_history_tracking_number
ATTACH PARTITION idx_parcel_history_2021_tracking_number; ALTER INDEX idx_parcel_history_tracking_number
ATTACH PARTITION idx_parcel_history_2022_tracking_number;

待所有子表都添加完索引后,校验父表 parcel_history ​索引标记是否有效:

-- 当前索引状态应该返回 TRUE
SELECT
c.relname AS index_name,
i.indisvalid AS is_valid
FROM pg_index i
JOIN pg_class c ON c.oid = i.indexrelid
WHERE c.relname = 'idx_parcel_history_tracking_number';

此步可忽略:校验阶段,添加一个新的分区表,查看新分区表是否添加了对应的索引:

CREATE TABLE parcel_history_2026  PARTITION OF parcel_history
FOR VALUES FROM ('2026-01-01') TO ('2027-01-01');

索引删除

假如子表的索引是由父表进行维护,则当通过子表去删除索引的时候,这个操作是不允许的,PostgreSQL ​会直接拒绝掉这个操作:

DROP INDEX idx_parcel_history_2025_tracking_number;
-- ERROR: cannot drop index parcel_2023_tracking_number_idx because index idx_parcel_history_tracking_number requires it
-- 建议:You can drop index idx_parcel_history_tracking_number instead.

所以,假如需要某个分区表的索引,则一定需要删除父表索引,子表的索引就会自动删除!

DROP INDEX idx_parcel_history_tracking_number;
-- completed in 400 ms

创建新分区

parcel_history ​数据表上面,创建 parcel_history_2026 ​新分区

CREATE TABLE parcel_history_2026  PARTITION OF parcel_history
FOR VALUES FROM ('2026-01-01') TO ('2027-01-01');

挂载分区

流程介绍

挂载分区的操作和创建新分区来对比,复杂了很多!因为创建新分区是生成一张全新的数据表,PostgreSQL 只需要维护对应的元数据(字段、索引、分区约束和主键等等),而挂载新分区的时候,因为待挂载的分区已经存在大量的数据了,在挂载到分区表之前,PostgreSQL 需要做一堆数据校验工作,以下是 PostgreSQL 文档的原文:

Note that when running the ATTACH PARTITION command, the table will be scanned to validate the partition constraint while holding an ACCESS EXCLUSIVE lock on that partition. As shown above, it is recommended to avoid this scan by creating a CHECK constraint matching the expected partition constraint on the table prior to attaching it. Once the ATTACH PARTITION is complete, it is recommended to drop the now-redundant CHECK ​constraint. If the table being attached is itself a partitioned table, then each of its sub-partitions will be recursively locked and scanned until either a suitable CHECK constraint is encountered or the leaf partitions are reached.

请注意,执行 ATTACH PARTITION 命令时,将对分区表加 ACCESS EXCLUSIVE 并扫描表内数据以验证分区约束。如前所述,建议在挂载分区之前,在目标表上预先创建一个与预期分区约束相匹配的 CHECK 约束以规避此扫描操作ATTACH PARTITION 操作完成后,建议删除此时已冗余的 CHECK 约束如果待挂载的表本身也是一个分区表,那么它的每个子分区都将被递归地加锁并扫描直到遇到匹配的 CHECK 约束或到达叶子分区为止

For each index in the target table, a corresponding one will be created in the attached table; or, if an equivalent index already exists, it will be attached to the target table's index, as if ALTER INDEX ATTACH PARTITION had been executed.

对于目标表中的每个索引,系统将在被挂载的表中新建一个对应索引;或者,若该表上已存在结构等效的索引,则直接将该索引挂载至目标表的索引层级——该操作等同于自动执行了 ALTER INDEX ATTACH PARTITION 命令

根据上面的官网信息,我们可以得知以下几点:

  • ATTACH PARTITION ​操作,会为数据表添加 ACCESS EXCLUSIVE(访问独占锁,阻塞该表的所有操作),这个操作将导致数据表锁死,严重影响业务系统的操作

  • ATTACH PARTITION ​操作,有两个比较耗时的操作,但是目前这两个操作,官方都提供了解决方案!

    • 对待添加的分区表添加 CHECK ​约束校验,校验改分区内的所有数据,是否都满足分区键的约束!
    • 校验待添加的分区表中,是否存在和父表的等效索引,以维护父表的索引在子表中的传递!

下面的流程图,是 DeepSeek ​对 1000w 的数据表执行 ATTACH PARTITION ​大致流程:

graph TD
A[开始ATTACH PARTITION] --> B[立即请求 ACCESS EXCLUSIVE 锁]
B --> C{是否预创建已验证的 CHECK 约束?}
C -- 是 --> D[跳过全表扫描<br>直接信任约束]
C -- 否 --> E[在锁保护下扫描数据<br>SSD:20-50min]
D --> F{新分区是否有等效索引?}
E --> F
F -- 无索引 --> G[在锁保护下创建索引<br>SSD:10-30min]
F -- 有索引 --> H[挂载索引<br>0.1-1s]
G --> I[更新元数据]
H --> I
I --> J[删除预创建约束]
J --> K[结束释放锁]

classDef red fill:#f9d5d5,stroke:#e88;
classDef green fill:#d5f0d5,stroke:#8e8;
class E,G red;
class D,H green;

由流程图可以得知,假如我们控制好约束和索引,则 ATTACH PARTITION ​基本上可以在秒级执行(这点我已经做过测试)!

实际操作-hot 表

目前我们需要将一张 4000w 的 parcel_hot ​挂载到 parcel ​数据表。

parcel ​表目前有如下特征:

  • 索引:有一个 tracking_number ​的索引,idx_parcel_tracking_number
  • 主键:主键为 id, archived, created_at,主键名称为 parcel_pkey

parcel_hot ​有如下特征:

  • 索引:无任何索引
  • 主键:有一个 id, created_at ​主键,主键名称为 parcel_hot_pkey
分区校验

首先,我们需要提前执行好 parcel_hot ​的分区约束,避免执行 ATTACH PARTITION ​时,锁表太长时间。parcel_hot ​分区约束比较简单:archived=FALSE。下面是约束执行的详细 SQL:

-- 添加 parcel_hot_archived_false 约束,并且只对新数据执行,老数据不执行校验
ALTER TABLE parcel_hot
ADD CONSTRAINT parcel_hot_archived_false
CHECK (archived = FALSE) NOT VALID;
-- completed in 244 ms -- 校验 parcel_hot_archived_false 老数据校验
ALTER TABLE parcel_hot
VALIDATE CONSTRAINT parcel_hot_archived_false;
-- completed in 37 s 561 ms
主键替换

目前 parcel_hot ​的主键为 id, created_at,而 parcel ​的主键为 id, archived, created_at,而这主键不一致,因此无法进行挂载,所以需要我们手动更换主键,对齐两张数据表的主键!下面是更换主键的 SQL 操作:

-- 增加新的主键
CREATE UNIQUE INDEX CONCURRENTLY parcel_hot_pkey_new
ON parcel_hot (id, archived, created_at);
-- Time: 160.987s -- 替换主键
BEGIN;
ALTER TABLE parcel_hot DROP CONSTRAINT parcel_hot_pkey;
ALTER TABLE parcel_hot ADD PRIMARY KEY USING INDEX parcel_hot_pkey_new;
COMMIT;
-- 此事务 0.5s 左右
等效索引

目前 parcel ​有一个 tracking_number ​索引,而 parcel_hot ​无任何索引,因此需要在 parcel_hot ​也添加 tracking_number ​索引。下面是添加索引的 SQL:

CREATE INDEX CONCURRENTLY idx_parcel_hot_tracking_number
ON parcel_hot (tracking_number);
-- completed in 53 s 704 ms
挂载分区

以上的操作钧执行完成后,现在就可以执行挂载分区的操作了!

ALTER TABLE parcel ATTACH PARTITION parcel_hot
FOR VALUES IN (FALSE)
-- Time: 0.277s
删除分区校验
ALTER TABLE parcel_hot
DROP CONSTRAINT parcel_hot_archived_false;

实际操作-history

目前我们需要将一张 4000w 的 parcel_history_2023 ​挂载到 parcel_history ​数据表。

parcel_history ​表目前有如下特征:

  • 索引:有一个 tracking_number ​的索引,idx_parcel_history_tracking_number
  • 主键:主键为 id, archived, created_at,主键名称为 parcel_history_pkey

parcel_history_2023 ​有如下特征:

  • 索引:无任何索引
  • 主键:有一个 id, created_at ​主键,主键名称为 parcel_history_2023_pkey
分区校验

首先,我们需要提前执行好 parcel_history_2023 ​的分区约束,避免执行 ATTACH PARTITION ​时,锁表太长时间。parcel_history_2023 ​分区约束比较简单:archived=FALSE AND created_at >= '2023-01-01' AND created_at < '2024-01-01'

注意:一定不能够将分区条件写为下面这样: archived=FALSE AND created_at BETWEEN '2023-01-01' AND '2024-01-01'

因为 FOR VALUES FROM ('2026-01-01') TO ('2027-01-01') ​的时间区间为:['2023-01-01 00:00:00', '2024-01-01 00:00:00)

BETWEEN '2023-01-01' AND '2024-01-01' ​的时间取件为:['2023-01-01 00:00:00', '2024-12-01 00:00:00]

当写成 archived=FALSE AND created_at BETWEEN '2023-01-01' AND '2024-01-01' ​这样,依旧会执行分区校验的 SQL,导致锁表时间大大的加长了!

下面是约束执行的详细 SQL:

-- 添加 parcel_hot_archived_false 约束,并且只对新数据执行,老数据不执行校验
ALTER TABLE parcel_history_2023
ADD CONSTRAINT parcel_history_2023_archived_true_created_at
CHECK (archived = TRUE AND created_at >= '2023-01-01' AND created_at < '2024-01-01') NOT VALID;
-- Time: 0.194s -- completed in 244 ms -- 校验 parcel_hot_archived_false 老数据校验
ALTER TABLE parcel_history_2023
VALIDATE CONSTRAINT parcel_history_2023_archived_true_created_at;
-- Time: 72.051s
主键替换

目前 parcel_history_2023 ​的主键为 id, created_at,而 parcel_history ​的主键为 id, archived, created_at,而这主键不一致,因此无法进行挂载,所以需要我们手动更换主键,对齐两张数据表的主键!下面是更换主键的 SQL 操作:

-- 增加新的主键
CREATE UNIQUE INDEX CONCURRENTLY parcel_history_2023_new_key
ON parcel_history_2023 (id, archived, created_at);
-- Time: 160.987s -- 替换主键
BEGIN;
ALTER TABLE parcel_hot DROP CONSTRAINT parcel_history_2023_pkey;
ALTER TABLE parcel_hot ADD PRIMARY KEY USING INDEX parcel_history_2023_pkey_new;
COMMIT;
-- 此事务 0.5s 左右
等效索引

目前 parcel_history ​有一个 tracking_number ​索引,而 parcel_history_2023 ​无任何索引,因此需要在 parcel_history_2023 ​也添加 tracking_number ​索引。下面是添加索引的 SQL:

CREATE INDEX CONCURRENTLY idx_parcel_history_2023_tracking_number
ON parcel_history_2023 (tracking_number);
-- completed in 53 s 704 ms
挂载分区

以上的操作钧执行完成后,现在就可以执行挂载分区的操作了!

ALTER TABLE parcel_history ATTACH PARTITION parcel_history_2023
FOR VALUES FROM ('2023-01-01') TO ('2024-01-01')
-- Time: 0.277s
删除分区校验
ALTER TABLE parcel_history_2023
DROP CONSTRAINT parcel_history_2023_archived_true_created_at;

卸载分区

卸载分区一般速度都比较快,所以相对来说比较安全。卸载分区后,卸载的分区将以独立的数据表存在,且不再与主表有任何关联。

parcel_history_2023 ​从 parcel_history ​分区卸载:

锁表卸载分区(锁的时间很短):

ALTER TABLE parcel_history  DETACH PARTITION parcel_history_2023;

并发卸载分区(不锁表):

ALTER TABLE parcel_history  DETACH PARTITION parcel_history_2023 CONCURRENTLY;


参考文档

PostgreSQL: Documentation: 17: ALTER TABLE

PostgreSQL: Documentation: 17: 5.12. Table Partitioning

PostgreSQL 分区最佳实践的更多相关文章

  1. 腾讯云TDSQL PostgreSQL版 -最佳实践 |优化 SQL 语句

    查看是否为分布键查询 postgres=# explain select * from tbase_1 where f1=1; QUERY PLAN ------------------------- ...

  2. 分布式 PostgreSQL 集群(Citus),分布式表中的分布列选择最佳实践

    确定应用程序类型 在 Citus 集群上运行高效查询要求数据在机器之间正确分布.这因应用程序类型及其查询模式而异. 大致上有两种应用程序在 Citus 上运行良好.数据建模的第一步是确定哪些应用程序类 ...

  3. [转载]再谈PostgreSQL的膨胀和vacuum机制及最佳实践

    本文转载自 www.postgres.cn 下的文章: 再谈PostgreSQL的膨胀和vacuum机制及最佳实践http://www.postgres.cn/news/viewone/1/390 还 ...

  4. MySQL性能优化的21个最佳实践

    http://www.searchdatabase.com.cn/showcontent_38045.htm MySQL性能优化的21个最佳实践 1. 为查询缓存优化你的查询 大多数的MySQL服务器 ...

  5. 基于AWS的云服务架构最佳实践

    ZZ from: http://blog.csdn.net/wireless_com/article/details/43305701 近年来,对于打造高度可扩展的应用程序,软件架构师们挖掘了若干相关 ...

  6. 可伸缩性最佳实践:来自eBay的经验

    看到一篇关于系统可伸缩性(可扩展)的文章,eBay的架构师Randy Shoup写的,原文出处没找到,就不写转载的地址了.根据自己的理解对文章有修改剪切的地方. 在eBay,可伸缩性是我们每天奋力抵抗 ...

  7. 基于开源软件在Azure平台建立大规模系统的最佳实践

    作者 王枫 发布于2014年5月28日 前言 Microsoft Azure 是微软公有云的唯一解决方案.借助这一平台,用户可以以多种方式部署和发布自己的应用. 这是一个开放的平台,除了对于Windo ...

  8. RHCS集群理论暨最佳实践

    RHCS集群理论暨 最佳实践 什么是集群?     集群是一组(>2)相互独立的,通过高速网络互联的计算机组成的集合.群集一般可以分为科学集群,负载均衡集群,高可用性集群三大类.     科学集 ...

  9. Apache Hadoop最佳实践和反模式

    摘要:本文介绍了在Apache Hadoop上运行应用程序的最佳实践,实际上,我们引入了网格模式(Grid Pattern)的概念,它和设计模式类似,它代表运行在网格(Grid)上的应用程序的可复用解 ...

  10. RDS最佳实践(一)—如何选择你的RDS

    在去年双11之前,为了帮助商家准备天猫双11的大促,让用户更好的使用RDS,把RDS的性能发挥到最佳,保障双11当天面对爆发性增加的压力,不会由于RDS的瓶颈导致系统出现问题,编写了 RDS的最佳实践 ...

随机推荐

  1. live555开发笔记(三):live555创建RTSP服务器源码剖析,创建h264文件rtsp服务器源码深度剖析

    前言   对于live555的rtsp服务器有了而基本的了解之后,进一步对示例源码进行剖析,熟悉整个h264文件流媒体的开发步骤.   Demo         播放本地文件,多路播放的时候,总是以第 ...

  2. .NET外挂系列:1. harmony 基本原理和骨架分析

    一:背景 1. 讲故事 为什么要开这么一个系列,是因为他可以对 .NET SDK 中的方法进行外挂,这种技术对解决程序的一些疑难杂症特别有用,在.NET高级调试 领域下大显神威,在我的训练营里也是花了 ...

  3. 第二次阶段性OOP题目集总结性Blog

    前言: 基础题目训练说明 第一次基础题目有两道,题量较少,通过对之前题目的进一步扩展,考察知识点主要是1.类的封装.继承.多态2.抽象类3.接口.题目主要考查了学生对代码结构和可扩展性优化的能力.难度 ...

  4. 简单说说C#中委托的使用-01

    简单说说C#中委托的使用-01 前言 距离上次更新文章,已经过去...月了. 没更新文章的主要原因,主要是因为参加工作后,感觉思维没有上学那会活跃,写文章没有思绪.再就是上班的时候把精力用光了,下班后 ...

  5. HTTP请求使用http、socks代理demo,包含有认证和无认证

    package cn.daenx.myadmin.email.utils; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpU ...

  6. python基础—内置函数

    1.数学运算类 (1).abs(x) 求绝对值,参数x可以是整形,也可也是复数,如果是复数,则返回复数的模 abs(-1) >> 1 (2).divmod(x,y) 返回两个数值的商和余数 ...

  7. 手把手教你网络爬虫(爬取豆瓣电影top250,附带源代码)

    概念 网络爬虫就是按照一定的规则,自动抓取互联网信息的程序或脚本.其本质就是模拟浏览器打开网页,获取网页中我们需要的数据. 基本流程 准备工作(构建流程) 获取数据 解析内容 保存数据 1. 准备工作 ...

  8. ES6中好用的方法

    1.关于扁平化数组 const deps = { 'A组':[1,2,3], 'B组':[5,8,12], 'C组':[5,14,79], 'D组':[3,64,105], } let member ...

  9. ChatMoney让你不再恋爱脑!

    本文由 ChatMoney团队出品 你是否曾经想过,为什么我们会在恋爱中变得如此"上头",仿佛整个世界都围绕着那个TA旋转? 恋爱脑,通常是指一个人在恋爱中过度投入.过度依赖对方, ...

  10. 性能比对 - C# 中 WaitForExit 卡死,当 Process.Start 调用 FFMPEG 解码 高清视频 时出现

    目的 这篇文章的意义,不在于解决问题本身,因为这次问题发生在一种错误的使用条件之下,但是在这个过程中发现了一些有趣的现象,有一些感悟,进行记录. 插入几句话:运行在客户端的CS架构的软件,比WBE网页 ...