如何将列表分区转换为哈希分区_直接转换限制与重建数据的过渡方案


MySQL不支持列表分区直接转哈希分区,必须重建表;需注意主键包含分区列、NULL值处理、哈希列离散性及在线迁移工具约束。

列表分区不能直接转成哈希分区

mysql 8.0 及之前所有版本都不支持 alter table ... partition by hash 直接重定义已存在列表分区的表。执行会报错:error 1503 (hy000): a primary key must include all columns in the table's partitioning function 或更常见的 error 1486 (hy000): constant, random or timezone-dependent expressions in (sub)partitioning function are not allowed——哪怕你只是想换分区逻辑,mysql 也会拒绝,因为底层元数据和数据文件结构不兼容。

根本原因在于:列表分区按显式值映射(VALUES IN (1,2,3)),哈希分区依赖取模运算(HASH(col) MOD N),两者的数据物理分布规则完全不同,无法就地 reinterpret。

重建表是唯一可靠路径

必须用 CREATE TABLE ... PARTITION BY HASH 新建结构,再把老数据导入。关键不是“能不能快”,而是“怎么避免锁表、丢数据、主从不一致”。

  • 如果表有主键且含分区列,新表的 PARTITION BY HASH 必须包含该列,否则建表失败;
  • 若原列表分区基于多列(如 PARTITION BY LIST COLUMNS(a,b)),哈希分区只能选其中一列做 HASH,多列哈希需先建计算列或改写为单列表达式;
  • 使用 INSERT INTO new_table SELECT * FROM old_table 前,确认新表无触发器、外键约束或自增列冲突,否则插入可能静默失败或跳过行;
  • 大表务必在从库先操作,验证数据一致性(比如用 pt-table-checksum),再主库切流;

在线迁移需绕过 DDL 阻塞

直接 RENAME TABLE 切换会锁全表,业务不可接受。稳妥做法是双写 + 校验 + 切流,但成本高;轻量级方案是用 pt-online-schema-change(简称 pt-osc)代理:

  • 它会自动建影子表、同步增量、原子重命名,但要求原表有主键或唯一非空索引,否则无法追踪变更;
  • 运行时注意 --chunk-size--max-lag,避免从库延迟飙升;
  • 哈希分区数建议设为 2 的幂(如 4/8/16),避免 MOD 运算引发数据倾斜;
  • 执行前停掉所有对原表的 LOAD DATA INFILE 或批量 INSERT ... ON DUPLICATE KEY UPDATE,它们可能绕过 pt-osc 的触发器捕获。

分区列类型与 NULL 处理差异要对齐

列表分区允许 NULL 单独作为一个分区(VALUES IN (NULL)),但哈希分区对 NULL 的处理是固定映射到分区 0,且无法显式控制。如果原列表分区里有大量 NULL 值,迁移到哈希后它们全挤进第一个分区,极易造成热点。

  • 迁移前用 SELECT COUNT(*) FROM old_table WHERE col IS NULL 检查比例;
  • 若占比高,要么提前用 COALESCE(col, -1) 替换 NULL 再导入,要么在新表中把哈希列设为 NOT NULL 并加默认值约束;
  • 字符串列做哈希时,注意 MySQL 默认用整数哈希(HASH(ASCII(SUBSTR(col,1,1)))),实际是取首字符 ASCII 值,不是全字段内容哈希——真要均匀分布得用 UNHEX(MD5(col)) % N 类表达式,但性能差,慎用。

哈希分区真正生效的前提,是分区列值分布足够离散。如果原列表分区里只有 3 个取值,强行改成 8 分区哈希,结果就是 7 个空分区 + 1 个满分区——这种数据特征比语法限制更难绕开。