在实际开发过程中Mysql该如何设置ID

核心原则

首先,记住一个核心原则:主键应该是简短、稳定、唯一且递增的

  • 简短: 占用存储空间小,索引更高效。
  • 稳定: 一旦生成,最好不要改变。更改主键会引发连锁的更新操作,非常昂贵。
  • 唯一: 这是主键的根本。
  • 递增: 对于使用 B+Tree 索引的 InnoDB 来说,递增的主键可以保证顺序写入,极大减少页分裂,提高插入性能。

方案一:自增整数(AUTO_INCREMENT) - 最常用

这是 MySQL 默认推荐的方式,也是绝大多数场景下的最佳选择

DDL 示例:

CREATE TABLE `users` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` VARCHAR(100) NOT NULL,
-- ... 其他字段
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

优点:

  1. 性能极佳: 插入新数据时无需为主键列指定值,数据库自动在当前最大值上加 1。顺序写入,效率最高,能充分利用索引的局部性原理。
  2. 存储空间小INT (4字节) 或 BIGINT (8字节) 类型非常紧凑。
  3. 简单可靠: 数据库自身保证唯一性和递增性,业务代码无需关心 ID 生成逻辑。

缺点:

  1. 不便分库分表: 在分布式数据库架构下,多个数据库实例可能会生成相同的 ID,导致冲突。需要额外的分布式 ID 生成方案(如设置不同实例的自增步长和偏移量,但这很复杂且不推荐)。
  2. 安全性问题: ID 是连续的,容易被爬虫爬取全部数据(例如,将 URL 中的 id=1 改为 id=2 即可访问下一条)。但这通常不是大问题,权限校验才是关键。
  3. 业务含义泄露: 有时我们不希望用户通过 ID 推测出公司的业务量(例如,用户 ID 为 10086,可能代表公司有 1 万多个用户)。

适用场景:

  • 几乎所有单机 MySQL 数据库
  • 非分布式的、简单的 Web 应用、后台管理系统、企业内部系统等。

方案二:UUID(Universally Unique Identifier) - 用于分布式系统

UUID 是一个 128 位(16字节)的数字,通常以 36 个字符的字符串形式表示(如 550e8400-e29b-41d4-a716-446655440000)。

DDL 示例:

CREATE TABLE `orders` (
`id` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '全局唯一主键',
`user_id` BIGINT UNSIGNED NOT NULL,
`amount` DECIMAL(10,2) NOT NULL,
-- ... 其他字段
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 或者使用 BINARY(16) 来存储,更节省空间,但可读性差

优点:

  1. 全局唯一: 这是它最大的优势。在分布式系统中,不同节点无需协调即可独立生成绝对不会冲突的 ID,非常适合分库分表场景。
  2. 安全性更高: ID 毫无规律,无法被猜测,一定程度上避免了数据被遍历的风险。

缺点:

  1. 性能灾难
    • 存储空间大: 字符串形式占用 36 字节,即使是优化为 BINARY(16) 也要 16 字节,是 BIGINT 的两倍。导致主键索引和二级索引都非常臃肿。
    • 插入性能差: UUID 是随机的,新插入的行无法简单地追加到索引末尾,而是需要插入到中间的某个位置,会导致大量的页分裂索引碎片,严重拖慢写入速度。
    • 缓存不友好: 随机 IO 无法利用磁盘和内存的缓存,查询性能也会受到影响。
  2. 可读性差: 对人类不友好,调试和排查问题时很不方便。

适用场景:

  • 需要提前生成 ID: 在事务提交前,业务就需要拿到 ID 用于后续操作。
  • 真正的分布式数据库系统(如分库分表的超大集群),且没有其他更好的分布式 ID 生成方案时。
  • 数据需要离线合并,且无法保证唯一性的场景。

强烈建议:除非你非常确定你的业务是分布式且需要提前知道 ID,否则不要使用 UUID 作为主键


方案三:雪花算法(Snowflake)及其变体 - 分布式系统的最佳实践

这是对“自增ID”和“UUID”的一种完美折中方案。它生成一个 64位(8字节)BIGINT 长整数,结构如下:

1bit (符号位,恒为0) 41bit (时间戳,毫秒级) 10bit (工作机器ID) 12bit (序列号)
0 00001010101010101010… 0000000001 000000000000

工作原理: 在同一毫秒内,同一台机器上的生成器通过递增序列号来生成多个 ID。如果毫秒数递增,序列号就重置为 0。

DDL 示例:
生成 ID 的工作通常在应用层完成(如 Java 程序使用 Hutool 等工具包),数据库只负责存储。

CREATE TABLE `distributed_table` (
`id` BIGINT UNSIGNED NOT NULL COMMENT '雪花算法生成的主键ID',
`data` VARCHAR(100) NOT NULL,
-- ... 其他字段
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

优点:

  1. 分布式友好: 通过分配不同的“机器ID”,不同节点生成的 ID 绝对不会冲突。
  2. 性能优异: 生成的 ID 是趋势递增的(整体上看是随时间变大的),对 InnoDB 的聚集索引非常友好,插入性能接近自增 ID。
  3. 存储空间小: 仅占 8 字节,和自增 BIGINT 一样。
  4. 高可用: 不依赖数据库自身,即使数据库短暂不可用,应用也能继续生成 ID。

缺点:

  1. 实现复杂: 需要在应用层引入额外的代码或服务(如独立的 ID 生成器)来保证“机器ID”的唯一性和时钟的正确性(防止时钟回拨)。
  2. 依赖系统时钟: 如果发生时钟回拨(服务器时间被调慢),可能会导致生成重复 ID。

适用场景:

  • 高并发的分布式系统、微服务架构。
  • 需要进行分库分表 的大型互联网应用。
  • 几乎所有需要全局唯一 ID 且对性能有要求的场景。

总结与决策图

为了帮助你快速做出选择,可以参考以下决策流程:

flowchart TD
A[开始选择主键ID方案] --> B{是否是分布式架构?};
B -- 否 --> C[使用<b>自增ID (AUTO_INCREMENT)</b><br>简单、高效、可靠];

B -- 是 --> D{是否需要提前知道ID?};
D -- 否 --> E[使用<b>数据库自增</b>并配置<br>不同步长和偏移量];
D -- 是 --> F{是否有开发运维能力<br>维护ID生成服务?};
F -- 否 --> G[使用<b>UUID</b>作为妥协<br>(注意性能损耗)];
F -- 是 --> H[使用<b>雪花算法</b>或其变体<br>(最佳分布式方案)];

最终建议:

  1. 默认选择: 对于 99% 的应用,直接使用 BIGINT UNSIGNED AUTO_INCREMENTBIGINT 的范围足够大到让你忘记溢出的问题(从 0 到 184亿亿)。
  2. 分布式架构: 如果你在做微服务或明确知道要分库分表,首选雪花算法或其变体(如美团Leaf、百度UidGenerator)。这是在性能、存储成本和分布式需求之间最好的权衡。
  3. 尽量避免使用 UUID: 除非你非常清楚它的代价并且能接受,或者你的场景极其特殊(如极高的离散性要求)。

额外提示

  • 永远不要用具有业务意义的字段(如手机号、身份证号、邮箱)做主键,因为业务含义可能会变化。
  • 主键就只负责唯一标识一行数据,它唯一的使命就是“短、稳、快”。