newid() 不适合直接作 sql server 主键,因其生成的随机 uniqueidentifier 导致聚簇索引频繁页分裂、高碎片率(常>60%)、插入吞吐量下降3–5倍;mysql 中 uuid() 同理,需转 binary(16) 并用 uuid_to_bin(..., 1) 或组合键/应用层有序 id 规避无序写入问题。

SQL Server 用 NEWID() 可以,但直接当主键有严重性能隐患;MySQL 8.0+ 用 UUID() 同理——它们生成的值无序,会导致聚簇索引频繁页分裂。
为什么 NEWID() 不适合直接作 SQL Server 主键
SQL Server 默认用主键建聚簇索引,而 NEWID() 返回的 uniqueidentifier 是完全随机的 16 字节值。新行总被插入到数据页中间或开头,引发大量页拆分和碎片。
- 插入吞吐量可能下降 3–5 倍(对比自增
INT) -
DBCC SHOWCONTIG或查询sys.dm_db_index_physical_stats会显示高逻辑碎片率(常 >60%) - 若表已有百万行,后续每万次插入就可能触发一次页面拆分
- 不加
NEWSEQUENTIALID()替代方案时,几乎无法靠索引重建彻底缓解
MySQL 中 UUID() 作为主键的实际表现
MySQL 8.0+ 的 UUID() 返回格式为 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx(36 字符字符串),默认存储为 CHAR(36),但更常见的是转成二进制存为 BINARY(16)。
- 直接用
UUID()作主键,InnoDB 同样因无序写入导致聚簇索引膨胀 -
UUID_SHORT()是替代选项,但它依赖服务器启动时间 + 服务器 ID + 自增计数器,**不是全局唯一**(跨实例可能重复) - 若坚持用 UUID,必须配合
UNHEX(REPLACE(UUID(), '-', ''))转为BINARY(16)存储,并在应用层保证生成逻辑不退化为纯随机 - 注意:MySQL 的
UUID()每次调用都重新生成,不能像 SQL Server 那样在列定义里写DEFAULT NEWID()—— 它不支持函数默认值(直到 8.0.13 才支持部分函数,默认仍不支持UUID())
真正可行的替代方案:有序 UUID / 组合键
核心思路是让 UUID 的前几位携带时间或序列信息,使插入位置相对可预测。
- SQL Server 推荐用
NEWSEQUENTIALID()(仅限默认约束,不可在 SELECT 中调用),它生成的值在重启后仍保持大致递增 - MySQL 可用
UUID_TO_BIN(UUID(), 1)(8.0.13+),第二个参数1表示将时间戳前置,生成的BINARY(16)具备一定顺序性 - 更稳妥的做法是「组合主键」:例如
(shard_id, uuid),其中shard_id是小整数(如 0–15),把写入分散到多个逻辑段,降低单页竞争 - 应用层生成更优的 ID(如 Twitter Snowflake、ULID、KSUID)再插入,数据库只做存储,避免依赖 RDBMS 内置 UUID 函数的不可控行为
真正难的不是生成唯一值,而是让这个唯一值不破坏索引结构。很多人卡在“怎么让 UUID 不重复”,却没意识到“重复”根本不是问题,“乱序写入导致性能崩塌”才是致命点。











