Guid.NewGuid() 是最简单稳妥的唯一ID生成方案,基于随机数、时间戳和硬件信息生成128位值,理论碰撞概率极低,实际可视为全局唯一;但需注意数据库索引性能、字符串格式优化及并发安全等问题。

用 Guid.NewGuid() 生成唯一 ID 是最简单也最稳妥的选择
绝大多数场景下,直接调用 Guid.NewGuid() 就够了。它基于随机数、时间戳和硬件信息生成 128 位值,在 .NET 运行时保证极高的碰撞概率下限(理论平均需生成 2⁶¹ 个才可能重复),实际项目中基本可视为“全局唯一”。
常见错误是手动拼接时间戳+随机数+线程ID——这既难保障唯一性,又容易在高并发或容器化部署中出问题(比如多实例同一毫秒、虚拟机时钟回拨)。
- 不要用
DateTime.Now.ToString("yyyyMMddHHmmssfff") + new Random().Next(1000,9999):时间精度有限、Random实例复用会导致重复 - 如果存数据库,字段类型优先选
uniqueidentifier(SQL Server)或UUID(PostgreSQL),而非varchar(36)—— 节省空间且索引效率更高 -
Guid.NewGuid()生成的是“随机 GUID”,不是顺序 GUID;若需按时间大致有序(减少索引分裂),可用Guid.CreateVersion4()的变体,但 .NET 原生不提供,需自行实现或引入第三方库如SequentialGuid
需要字符串 ID 时,别直接用 ToString() 默认格式
Guid 转字符串默认带连字符和小写(如 "a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8"),占 36 字符。若用于 URL、日志或短链,建议压缩:
- 去掉连字符:
guid.ToString("N")→"a1b2c3d4e5f67890g1h2i3j4k5l6m7n8"(32 字符) - 转大写:
guid.ToString("D").ToUpperInvariant()或更高效地用guid.ToString("N").ToUpperInvariant() - 极端精简场景(如内部消息 ID)可用 Base32 编码,但需额外依赖,且无标准库支持;不推荐为省几个字符引入复杂度
- 避免用
Convert.ToBase64String():结果含/和+,在 URL 中需额外编码,反而增加处理成本
并发环境生成 ID 不能依赖 static Random
有人试图用静态 Random 实例配合时间戳生成“类 GUID”字符串,这是典型陷阱。因为 Random 不是线程安全的,多线程调用 Next() 可能返回相同值,尤其在 new Random() 频繁创建时(种子来自系统时钟,毫秒级精度,高并发下极易重复)。
- 绝对不要写
private static readonly Random _rnd = new Random();然后在方法里调_rnd.Next()生成 ID - 若真需整数型唯一 ID(比如订单号后缀),应改用
Interlocked.Increment(ref _counter)+ 时间前缀,或直接用数据库自增列 + 应用层组合 - .NET 6+ 推荐用
System.Diagnostics.Activity.Current?.Id ?? ActivityTraceId.CreateRandom().ToString(),它底层也是 GUID,但语义更明确,适合分布式追踪上下文
数据库主键用 GUID 要注意性能与聚集索引
SQL Server 默认对 uniqueidentifier 字段建聚集索引时,随机 GUID 会导致频繁页分裂和索引碎片,写入性能明显下降。这不是 C# 层的问题,但直接影响 ID 生成策略是否合理。
- 若必须用 GUID 当主键,SQL Server 上建议设为
NONCLUSTERED,另建IDENTITY列作聚集索引 - PostgreSQL 的
UUID类型无此问题,B-tree 索引对随机 UUID 处理良好 - 若业务允许,考虑用雪花算法(如
Twitter.Snowflake或IdGen库)生成 64 位整数 ID:有序、紧凑、无网络依赖,但需维护 worker ID 分配逻辑
真正难的不是生成唯一 ID,而是想清楚“唯一”的边界在哪里:是单机进程内?服务实例间?还是跨年跨数据库迁移后仍不冲突?选 Guid.NewGuid() 没错,但得知道它为什么对,以及在哪种存储或分片架构下会悄悄拖慢系统。











