17370845950

如何用分区表(PARTITION BY RANGE)解决大表更新慢
RANGE分区加速UPDATE的根本原因是分区裁剪使数据库跳过无关数据页。当WHERE条件含分区键的等值或范围查询且未被函数包裹时,系统仅扫描目标分区,大幅减少I/O;否则无法裁剪,性能无提升。

为什么 RANGE 分区能加速大表 UPDATE

根本原因不是分区本身快,而是它让 UPDATE 能跳过大量无关数据页。当 WHERE 条件命中分区键(比如 WHERE created_at >= '2025-01-01'),PostgreSQL 或 MySQL 会自动裁剪(prune)掉不满足范围的分区,只扫描目标分区内的数据——物理上少读几十 GB,自然快得多。

但前提是:UPDATE 的 WHERE 必须包含分区键的等值或范围条件,且不能被函数包裹(比如 WHERE DATE(created_at) = '2025-01-01' 就无法裁剪)。

建表时必须显式指定分区键和边界

RANGE 分区不像 HASH 那样“自动分”,你得自己定义每个分区覆盖哪一段值。常见错误是边界写错导致数据写入失败或跨区。

  • MySQL 示例:CREATE TABLE logs (id BIGINT, created_at DATETIME) PARTITION BY RANGE (TO_DAYS(created_at)) (PARTITION p2025 VALUES LESS THAN (TO_DAYS('2025-01-01')), PARTITION p2025 VALUES LESS THAN (TO_DAYS('2025-01-01')));
  • PostgreSQL 示例:CREATE TABLE logs (id BIGINT, created_at DATE) PARTITION BY RANGE (created_at); CREATE TABLE logs_2025 PARTITION OF logs FOR VALUES FROM ('2025-01-01') TO ('2025-01-01');
  • 边界必须严格递增、无重叠,且覆盖未来可能写入的数据范围;否则 INSERT 会报 no partition for partitioning key

UPDATE 前务必检查执行计划是否真正裁剪分区

很多人以为加了分区就自动优化,结果 EXPLAIN 一看还是全分区扫描。关键看输出里有没有 Partition Filter(PostgreSQL)或 partitions: p2025(MySQL)。

  • 确保 WHERE 中分区键未被隐式转换:比如字段是 DATE 类型,就别传 '2025-01-01 12:00:00' 字符串
  • 避免在分区键上用函数:WHERE YEAR(created_at) = 2025 → 改成 WHERE created_at >= '2025-01-01' AND created_at
  • 复合条件要小心:如果同时有 WHERE created_at > ? AND status = 'pending',而 status 没建索引,仍可能触发全分区扫描

分区维护比想象中更重,别只顾写入不管清理

旧分区不会自动删除,长期运行后会出现几百个分区,导致查询计划生成变慢、DDL 操作卡顿。DROP 分区虽快,但需手动调度,且不能影响正在执行的 UPDATE。

  • MySQL:用 ALTER TABLE logs DROP PARTITION p2025,但注意该分区所有数据立即丢失,不可回滚
  • PostgreSQL:先 DETACH PARTITION logs_2025,再 DROP TABLE logs_2025,更安全但步骤多
  • 切勿在业务高峰期做 REORGANIZE PARTITION 或新增分区,这类操作会锁整个父表

最易被忽略的一点:分区键一旦选错(比如用 user_id 而非时间),后续几乎无法通过 UPDATE 语句受益——因为业务更新通常按状态或 ID,而非按用户哈希范围。