UUID插入必然导致索引碎片,因InnoDB聚簇索引按主键物理排序而UUID随机;MySQL 8.0+应使用UUID_TO_BIN(uuid,1)+BINARY(16),SQL Server改用NEWSEQUENTIALID()。

直接用 UUID() 插入大量数据,一定会引发严重索引碎片——不是“可能”,而是必然。根本原因在于:InnoDB 聚簇索引按主键物理排序,而随机 UUID 会让新行不断插在页中间,触发频繁页分裂。解决路径只有两条:让 UUID 本身变有序,或彻底绕过 UUID 的随机性。
MySQL 8.0+ 必须用 UUID_TO_BIN(uuid, 1) + BINARY(16)
这是目前最可靠、开箱即用的方案。关键不是转二进制,而是第二个参数 1:它会把时间戳高位字节移到最前,使生成值近似单调递增。
- 建表必须声明为
id BINARY(16) PRIMARY KEY DEFAULT (UUID_TO_BIN(UUID(), 1)),不能用CHAR(36)或漏掉1 - 手动插入时,务必统一用
UUID_TO_BIN('xxx-xxx', 1);用UUID_TO_BIN('xxx-xxx', 0)或不带参数,就退回随机序 - 查询展示需用
BIN_TO_UUID(id, 1),参数必须和插入时一致,否则解码错乱 - 旧表加默认值不能只改列类型,得用
ALTER TABLE t ALTER COLUMN id SET DEFAULT (UUID_TO_BIN(UUID(), 1))
SQL Server 请放弃 NEWID(),改用 NEWSEQUENTIALID()
NEWSEQUENTIALID() 是唯一能真正缓解碎片的内置方案,但它有硬约束:只能作为列的默认约束,不能在 SELECT 或函数中调用。
- 添加方式只能是 T-SQL:
ALTER TABLE logs ADD CONSTRAINT DF_logs_id DEFAULT NEWSEQUENTIALID() FOR id - 插入时必须省略该列,不能显式传值;已存在的数据不受影响,
DEFAULT只对新行生效 - SSMS 图形界面无法设置,执行失败别怀疑界面,是语法不支持
- 如果主键不是聚集索引(比如主键是
INT IDENTITY,而UNIQUEIDENTIFIER是非聚集唯一列),那NEWID()其实无害
批量插入必须用多值 INSERT INTO ... VALUES (),(),()
哪怕主键已有序,单行 INSERT 仍会放大锁开销和日志写入频率,加速碎片累积。优化重点不在 UUID 本身,而在写入节奏。
- 单批控制在 500~1000 行之间;太少起不到合并效果,太多易触发长事务或 OOM
- 每批必须显式包裹事务:
START TRANSACTION→ 批量INSERT→COMMIT,禁用自动提交 - 超大导入(百万级)优先用
LOAD DATA INFILE,它跳过 SQL 解析层,性能高 5–10 倍 - 若用
LOAD DATA INFILE,源文件必须按主键升序排列,否则顺序优势归零
最容易被忽略的一点:所有“有序 UUID”方案都依赖字节序重排逻辑,MySQL UUID_TO_BIN(uuid, 1) 和 SQL Server NEWSEQUENTIALID() 都只作用于新插入行。已有表里混着随机 UUID 数据,重建索引或 OPTIMIZE 并不能让它们变有序——要么清洗旧数据,要么接受混合排序带来的局部碎片。











