灯下哥谭 灯下哥谭
首页
关于
  • Hermes Agent 平台
  • Claude Code
  • OpenClaw
  • GPU 推理节点运维
  • DeepSeek Harness
  • MySQL 运维知识地图
  • Elasticsearch 运维知识地图
  • Redis 运维知识地图
  • TiDB 体系
  • DBA 常用 SQL 与命令
  • Nginx 运维知识地图
  • Prometheus 监控
  • Docker
  • Systemd
  • Iptables
  • Firewalld
  • Sshd
  • MySQL8 运维 SOP 手册
  • MySQL 实战 45 讲(读书笔记)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

灯下哥谭

灯还亮着
首页
关于
  • Hermes Agent 平台
  • Claude Code
  • OpenClaw
  • GPU 推理节点运维
  • DeepSeek Harness
  • MySQL 运维知识地图
  • Elasticsearch 运维知识地图
  • Redis 运维知识地图
  • TiDB 体系
  • DBA 常用 SQL 与命令
  • Nginx 运维知识地图
  • Prometheus 监控
  • Docker
  • Systemd
  • Iptables
  • Firewalld
  • Sshd
  • MySQL8 运维 SOP 手册
  • MySQL 实战 45 讲(读书笔记)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • MySQL

  • Redis

  • 高性能KV

  • TiDB

    • TiCDC同步数据到Kafka
    • 对TiDB中算子的深入理解
    • TiDB使用 TTL (Time to Live) 定期删除过期数据
    • 如何移除TiDB中的表分区
      • 识别分区表
      • 方案一:直接 DDL 移除分区(推荐,仅限 v7.5.3+)
        • 执行步骤
        • 验证移除结果
      • 方案二:拷贝搬迁法(降级为备选)
        • 执行步骤
        • 拷贝搬迁法的代价
      • 坑与边界
        • 坑一:v7.5.3 以下 REMOVE PARTITIONING 存在数据丢失 bug
        • 坑二:REMOVE PARTITIONING 期间写入阻塞
        • 坑三:原表有外键或触发器
        • 坑四:分区键是主键的一部分
        • 坑五:TiFlash 副本同步延迟
      • 可复用要点
    • TiDB配置文件调优
    • 深入解析TiFlash:原理、适用场景与调优实践
    • tidb fast ddl
    • TiKV 运行满 2 年会 panic:795 天单调时钟溢出 bug(tikv#11940)复盘与预警建设
    • TiKV 节点 CPU 周期性打满,进程却只占 4%:一次热点 Region 的逆向排查
  • Elasticsearch

  • 数据管道

  • 其他数据库

  • 数据库
  • TiDB
灯下哥谭
2025-04-03
目录

如何移除TiDB中的表分区原创

分区表在数据量增长过快时可能成为瓶颈——查询优化器需要遍历过多分区元数据,分区裁剪失效导致全表扫描。当分区策略不再适合业务(如从按天分区的日志表转为冷归档),移除分区并转为普通表配合索引是常见优化路径。TiDB 支持 ALTER TABLE ... REMOVE PARTITIONING 单行 DDL,绝大多数场景无需手动搬迁数据。

版本说明

官方分区文档描述了 ALTER TABLE ... REMOVE PARTITIONING 的行为,但未标注该语法的引入版本。使用前请在自己的版本上用语句预览或测试集群确认可用性。

⚠️ 数据丢失风险:REMOVE PARTITIONING 在 v7.5.3 之前存在导致数据丢失的 bug(pingcap/tidb#53385 (opens new window)),v7.5.3 起修复。v7.5.3 以下版本严禁使用方案一,请改用方案二的拷贝搬迁法。

REMOVE PARTITIONING 属于 Reorg-Data 类 DDL,执行时会复制全表数据并重建索引。本文内容基于文档与源码分析,未在 TiDB 实例上实测,生产环境操作前请在测试集群验证。

# 识别分区表

在移除分区前,需确认哪些表使用了分区。TiDB 的 INFORMATION_SCHEMA.TABLES 提供了 CREATE_OPTIONS 字段标识分区状态:

SELECT 
    TABLE_SCHEMA, 
    TABLE_NAME,
    CREATE_OPTIONS
FROM INFORMATION_SCHEMA.TABLES 
WHERE CREATE_OPTIONS LIKE '%partitioned%';
1
2
3
4
5
6

预期输出示例:

+--------------+----------------+----------------+
| TABLE_SCHEMA | TABLE_NAME     | CREATE_OPTIONS |
+--------------+----------------+----------------+
| test_db      | user_activity  | partitioned    |
| test_db      | order_data     | partitioned    |
+--------------+----------------+----------------+
1
2
3
4
5
6

补充确认分区类型(Range、Hash、List 等):

SELECT 
    TABLE_NAME,
    PARTITION_NAME,
    PARTITION_METHOD,
    PARTITION_EXPRESSION
FROM INFORMATION_SCHEMA.PARTITIONS
WHERE TABLE_SCHEMA = 'test_db' 
  AND TABLE_NAME = 'user_activity';
1
2
3
4
5
6
7
8

预期输出示例:

+---------------+----------------+------------------+--------------------------------+
| TABLE_NAME    | PARTITION_NAME | PARTITION_METHOD | PARTITION_EXPRESSION           |
+---------------+----------------+------------------+--------------------------------+
| user_activity | p0             | RANGE            | to_days(`activity_date`)       |
| user_activity | p1             | RANGE            | to_days(`activity_date`)       |
| user_activity | p2             | RANGE            | to_days(`activity_date`)       |
+---------------+----------------+------------------+--------------------------------+
1
2
3
4
5
6
7

记录分区数量和行数分布,作为移除后的对比基准:

SELECT 
    PARTITION_NAME,
    TABLE_ROWS
FROM INFORMATION_SCHEMA.PARTITIONS
WHERE TABLE_SCHEMA = 'test_db' 
  AND TABLE_NAME = 'user_activity'
ORDER BY PARTITION_NAME;
1
2
3
4
5
6
7

# 方案一:直接 DDL 移除分区(推荐,仅限 v7.5.3+)

版本分界

v7.5.3 以下版本严禁使用此方案。REMOVE PARTITIONING 存在 pingcap/tidb#53385 (opens new window) 数据丢失 bug,v7.5.3 起修复。低版本请改用方案二的拷贝搬迁法。

官方文档提供单行 DDL 指令原地移除分区,内部流程为:复制全表数据到新的非分区表结构→重建索引→原子切换元数据。

# 执行步骤

步骤 1:确认表结构(记录以备回滚参考)

SHOW CREATE TABLE test_db.user_activity\G
1

预期输出关键部分:

CREATE TABLE `user_activity` (
  `id` bigint(20) NOT NULL,
  `activity_date` date NOT NULL,
  `user_id` bigint(20) DEFAULT NULL,
  `action` varchar(64) DEFAULT NULL,
  PRIMARY KEY (`id`,`activity_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
PARTITION BY RANGE (to_days(`activity_date`))
( PARTITION p0 VALUES LESS THAN (738950),
  PARTITION p1 VALUES LESS THAN (738981),
  PARTITION p2 VALUES LESS THAN MAXVALUE)
1
2
3
4
5
6
7
8
9
10
11

步骤 2:执行 REMOVE PARTITIONING

ALTER TABLE test_db.user_activity REMOVE PARTITIONING;
1

为什么要分两步而非原六步:旧教程推荐的 "创建新表→REMOVE PARTITIONING→加索引→迁数据→DROP 原表→重命名" 流程是错误冗余的。REMOVE PARTITIONING 本身就是原子操作,内部已包含数据复制和索引重建。额外手动搬迁不仅浪费时间,还引入 DROP 后 RENAME 失败导致数据丢失的风险。

步骤 3:添加替代索引

移除分区后,原本依赖分区裁剪的查询需要索引支撑。根据业务查询模式添加索引:

ALTER TABLE test_db.user_activity ADD INDEX idx_activity_date (activity_date);
ALTER TABLE test_db.user_activity ADD INDEX idx_user_id (user_id);
1
2

索引列选择原则:

  • 若原查询多用 WHERE activity_date BETWEEN ...,优先在 activity_date 建索引
  • 若原查询多用 WHERE user_id = ...,在 user_id 建索引
  • 复合索引顺序遵循最左前缀原则

# 验证移除结果

确认 SHOW CREATE TABLE 输出中 PARTITION BY 子句已消失:

SHOW CREATE TABLE test_db.user_activity\G
1

预期输出(关键差异):

CREATE TABLE `user_activity` (
  `id` bigint(20) NOT NULL,
  `activity_date` date NOT NULL,
  `user_id` bigint(20) DEFAULT NULL,
  `action` varchar(64) DEFAULT NULL,
  PRIMARY KEY (`id`,`activity_date`),
  KEY `idx_activity_date` (`activity_date`),
  KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
1
2
3
4
5
6
7
8
9

确认行数一致:

SELECT COUNT(*) FROM test_db.user_activity;
1

对比移除前各分区行数之和,应完全相等。

确认执行计划使用索引而非全表扫描:

EXPLAIN SELECT * FROM test_db.user_activity WHERE activity_date = '2024-01-15';
1

预期输出应包含 IndexRangeScan 或 IndexLookUp,而非 TableFullScan。

# 方案二:拷贝搬迁法(降级为备选)

直接 DDL 虽好,但在特定场景下拷贝搬迁法仍有价值:

适用场景:

  1. 超大表 DDL 阻塞风险:TiDB 的 Reorg-Data DDL 会复制全表数据,若表超过 100GB,DDL 执行期间可能阻塞同表的其他写入(视 tidb_ddl_reorg_batch_size 和系统负载而定)
  2. 灰度验证需求:需要在新结构上跑业务 SQL 验证性能,确认无误后再切换
  3. 版本低于 v7.5.3:受 #53385 (opens new window) 数据丢失 bug 影响,方案一不可用;更早的版本还可能完全不支持该语法(官方未标注引入版本,需自行确认)

# 执行步骤

步骤 1:创建结构一致的新表

CREATE TABLE test_db.user_activity_new LIKE test_db.user_activity;
1

步骤 2:新表移除分区

ALTER TABLE test_db.user_activity_new REMOVE PARTITIONING;
1

步骤 3:添加索引

ALTER TABLE test_db.user_activity_new ADD INDEX idx_activity_date (activity_date);
1

步骤 4:分批迁移数据(关键:避免事务超限)

TiDB 默认事务大小限制为 1GB(txn-total-size-limit),超大表 INSERT ... SELECT 可能超限。改用分批迁移:

-- 假设 activity_date 有连续分布,按日期分批
INSERT INTO test_db.user_activity_new 
SELECT * FROM test_db.user_activity 
WHERE activity_date >= '2024-01-01' AND activity_date < '2024-02-01';

INSERT INTO test_db.user_activity_new 
SELECT * FROM test_db.user_activity 
WHERE activity_date >= '2024-02-01' AND activity_date < '2024-03-01';
1
2
3
4
5
6
7
8

每批执行后验证:

SELECT COUNT(*) FROM test_db.user_activity_new;
1

步骤 5:原子切换(RENAME,非 DROP 先)

正确的切换顺序:先重命名原表保留,再重命名新表顶替,DROP 放在最后且手动执行。

-- 在同一事务中执行
RENAME TABLE 
  test_db.user_activity TO test_db.user_activity_backup,
  test_db.user_activity_new TO test_db.user_activity;
1
2
3
4

RENAME 操作是原子的,不会出现表名空白期。

步骤 6:业务验证后清理

确认业务读写正常、数据完整后,手动删除备份表:

DROP TABLE test_db.user_activity_backup;
1

# 拷贝搬迁法的代价

代价项 说明
双倍存储 迁移期间新老表同时存在,需确保磁盘充足
事务限流 大批量 INSERT 需手动切分,避免 txn-total-size-limit
应用层适配 若表名切换需配合应用配置变更,比原地 DDL 复杂

# 坑与边界

# 坑一:v7.5.3 以下 REMOVE PARTITIONING 存在数据丢失 bug

REMOVE PARTITIONING 在 v7.5.3 之前存在致命 bug(pingcap/tidb#53385 (opens new window)),执行后可能导致数据丢失。官方 v7.5.3 release notes 明确说明:"Fix the issue that executing ALTER TABLE ... REMOVE PARTITIONING might cause data loss"。

影响范围:所有 v7.5.3 以下版本,无论表大小、分区类型。

解药:

  • v7.5.3 及以上:可使用方案一
  • v7.5.3 以下:严禁使用方案一,改用方案二的拷贝搬迁法

验证当前版本:

SELECT tidb_version()\G
1

# 坑二:REMOVE PARTITIONING 期间写入阻塞

REMOVE PARTITIONING 属于 Reorg-Data 类 DDL(与添加索引、修改列类型同级)。执行期间,涉及该表的写入可能被阻塞,阻塞时长与数据量成正比。

缓解方案:

  1. 选择业务低峰期执行
  2. TiDB v6.2.0+ 支持 tidb_ddl_enable_fast_reorg,开启后可加速数据重组
  3. 超大表优先考虑方案二的拷贝搬迁法,将风险分散到可控的分批 INSERT

# 坑三:原表有外键或触发器

TiDB 支持外键(v6.6.0+ 实验性),若分区表被外键引用,REMOVE PARTITIONING 会报错。

检查外键依赖:

SELECT 
    TABLE_NAME,
    CONSTRAINT_NAME,
    REFERENCED_TABLE_NAME
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
WHERE REFERENCED_TABLE_NAME = 'user_activity';
1
2
3
4
5
6

若有外键,需先在外键 referencing 表上执行 DROP FOREIGN KEY,或改用方案二并同步更新外键指向。

# 坑四:分区键是主键的一部分

若主键定义为 PRIMARY KEY (id, activity_date),且 activity_date 是分区键,移除分区后主键不变,但数据物理存储布局改变。这对正确性无影响,但 EXPLAIN 中的分区裁剪会消失,必须依赖索引保证性能。

# 坑五:TiFlash 副本同步延迟

若表有 TiFlash 副本,REMOVE PARTITIONING 后 TiFlash 需重新同步数据。通过以下命令检查同步进度:

SELECT * FROM information_schema.tiflash_replica 
WHERE TABLE_SCHEMA = 'test_db' AND TABLE_NAME = 'user_activity';
1
2

确认 AVAILABLE 列为 1 后再将查询路由到 TiFlash。

# 可复用要点

  1. v7.5.3+ 才可用方案一:ALTER TABLE t REMOVE PARTITIONING 在 v7.5.3 之前存在数据丢失 bug(#53385),v7.5.3 以下必须改用方案二。

  2. 拷贝搬迁法保留给超大表或低版本:表大于 100GB、需要灰度验证,或版本低于 v7.5.3 时,用分批 INSERT + RENAME 切换替代原地 DDL。

  3. 事务大小限制是分批迁移的真正原因:TiDB 的 txn-total-size-limit 默认 1GB,超大表迁移必须按业务维度(如日期)切分批次。

  4. 验证务必覆盖元数据和执行计划:SHOW CREATE TABLE 确认分区语法消失、COUNT(*) 确认行数一致、EXPLAIN 确认查询走索引。

  5. TiFlash 副本需额外关注同步状态:分区结构变更会触发 TiFlash 重新加载,确认 AVAILABLE=1 后再切换分析负载。


🤖 Agent 可直接解析的元数据块(点击展开)
{
  "_meta": {
    "doc_version": "2025-04-03",
    "article_id": "tidb-remove-partition",
    "profile_context": "any",
    "estimated_setup_time": "30min",
    "version_requirement": "TiDB >= v7.5.3 for REMOVE PARTITIONING (data loss bug #53385 fixed in v7.5.3)"
  },
  "quick_start": {
    "identify_partitioned_tables": "SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE CREATE_OPTIONS LIKE '%partitioned%';",
    "remove_partitioning": "ALTER TABLE db.table REMOVE PARTITIONING;",
    "add_index": "ALTER TABLE db.table ADD INDEX idx_col (col);",
    "verify_structure": "SHOW CREATE TABLE db.table;",
    "verify_count": "SELECT COUNT(*) FROM db.table;",
    "verify_plan": "EXPLAIN SELECT * FROM db.table WHERE col = 'value';"
  },
  "safety_rules": [
    "REMOVE PARTITIONING 属于 Reorg-Data DDL,执行期间可能阻塞写入,选择低峰期",
    "超大表(>100GB)优先考虑拷贝搬迁法,分散风险",
    "拷贝搬迁法必须按 txn-total-size-limit 分批 INSERT,避免事务超限",
    "RENAME 切换顺序:原表→备份名,新表→原表名;DROP 放在最后手动执行"
  ],
  "verification": {
    "partition_removed": "SHOW CREATE TABLE 输出中无 PARTITION BY 子句",
    "row_count_match": "移除后 COUNT(*) 等于移除前各分区行数之和",
    "index_used": "EXPLAIN 显示 IndexRangeScan 而非 TableFullScan",
    "tiflash_ready": "SELECT AVAILABLE FROM information_schema.tiflash_replica WHERE TABLE_NAME='table'"
  },
  "troubleshooting": {
    "ddl_blocked": "检查 tidb_ddl_reorg_batch_size 和系统负载,必要时开启 tidb_ddl_enable_fast_reorg",
    "txn_too_large": "分批 INSERT,按业务日期或 ID 范围切分",
    "foreign_key_error": "先在外键 referencing 表执行 DROP FOREIGN KEY"
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#性能优化#容量规划
上次更新: 9/11/2026

← TiDB使用 TTL (Time to Live) 定期删除过期数据 TiDB配置文件调优→

最近更新
01
DeepSeek Harness 实战 06|学习笔记:插件、工具、技能不在同一个维度上 原创
09-11
02
DeepSeek Harness 实战 05|让两个编码 Agent 共用一份长期记忆 原创
09-09
03
DeepSeek Harness 实战 04|学习笔记:从「已知限制」里读出三处设计张力 原创
09-08
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式