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 个满分区——这种数据特征比语法限制更难绕开。










