uuidv7
要点
- 只要开始做登录和鉴权,系统里很快就会出现一堆需要唯一标识的对象
- uuidv7 的核心价值可以概括成一句话
- 理解 uuidv7,最重要的不是把每一位都背下来,而是抓住它的两段式结构,即
- 假设系统在 10:00:00 这一分钟里连续创建了三条 session 记录
- 这里还要专门澄清一个误区:uuidv7 不是数据库自增 id 那种严格单调递增
内容
1. 概述
接下来要开始系统介绍登录和鉴权方案,但在真正进入 session、token、cookie、权限边界之前,还有一个经常被低估的基础问题要先讲清楚:主键和业务标识到底该怎么生成。
这个问题看起来像数据库细节,实际会一路影响到:
- 用户 id 怎么发放
- session id 怎么生成
- 鉴权 token 关联什么主键
- 数据库索引写入是否平稳
- 分布式环境里是否能安全本地生成 id
当前项目又是基于 Cloudflare 生态来设计的,这意味着运行环境天然更偏分布式、边缘化、多实例。到了这种场景里,「靠数据库自增 id 统一发号」并不是最好的方案。
这篇文章先不讲登录流程本身,而是先把 uuidv7 的原理和使用方式讲清楚:为什么现在值得用它,它比 uuidv4、自增 id、ULID、NanoID、Snowflake 这些方案好在哪里,又分别会带来什么取舍。
2. 在鉴权系统里,id 方案为什么这么重要
只要开始做登录和鉴权,系统里很快就会出现一堆需要唯一标识的对象:
- 用户 id
- session id
- refresh token 记录 id
- 登录设备 id
- 审计日志 id
- 验证码或挑战记录 id
如果 id 方案选得不合适,问题通常会在两个地方最先暴露。
第一,数据库写入和索引性能。
比如完全随机的主键,会让 B+Tree 索引持续随机插入,页分裂更频繁,写入局部性更差。
第二,分布式生成和跨系统流转。
如果 id 必须依赖中心化发号器,边缘环境和多实例服务会天然多一层协调成本。只要这个发号链路抖一下,所有创建逻辑都会跟着受影响。
也就是说,这个问题不是单纯的「用什么库生成字符串」,它是数据库、分布式架构和业务可扩展性三件事的交叉点。
3. uuidv7 的核心价值
uuidv7 的核心价值可以概括成一句话:
它既保持了 UUID 这种分布式可本地生成的唯一性,又把时间顺序带进了 id 本身。
这一点很关键。
传统 uuidv4 最大的问题不是不唯一,而是太随机。它完全随机意味着:
- 排序没有时间含义
- 索引插入局部性差
- 仅凭 id 不能粗看先后顺序
uuidv7 则把高位时间戳和低位随机性组合起来,得到两个很实用的特性:
- 大体按生成时间递增
- 仍然能在分布式场景下本地生成
这正好击中了现代业务系统最常见的需求:
- 不想依赖中心化发号器
- 又希望主键在数据库里别太随机
- 还想让日志、记录、session 在时间上有自然顺序
所以 uuidv7 并不是在追求“绝对有序”,它追求的是一种很工程化的平衡:足够有序 + 足够分布式 + 足够通用。
4. uuidv7 的生成原理
理解 uuidv7,最重要的不是把每一位都背下来,而是抓住它的两段式结构,即:
- 前半段放时间信息
- 后半段放随机信息
可以把它粗略理解成:
// index.txt
uuidv7 = 时间戳 + 版本标记 + 随机位这里的时间戳使用的是 Unix epoch 毫秒时间,也就是当前时间距离 1970-01-01 00:00:00 UTC 的毫秒数。
这意味着同一时间线里,越晚生成的 uuidv7,高位时间部分通常越大,所以整体上会呈现出接近递增的趋势。
但如果只有时间戳还不够,因为同一毫秒内完全可能生成很多个 id。所以 uuidv7 后半段还保留了足够的随机位,用来保证同一毫秒内的大量生成仍然不会冲突。
所以它的设计思路其实很清楚:
- 时间部分解决「大体有序」
- 随机部分解决「高并发下唯一」
这也是为什么它特别适合现代后端系统。它不像自增 id 那样依赖中心发号,也不像 uuidv4 那样完全随机,而是卡在一个非常实用的中间点上。
5. 具体例子
假设系统在 10:00:00 这一分钟里连续创建了三条 session 记录:
- 第一条:用户 A 登录
- 第二条:用户 B 登录
- 第三条:用户 A 刷新 token
如果用 uuidv4,三条 id 可能像这样:
// index.txt
f47ac10b-58cc-4372-a567-0e02b2c3d479
9d2f2b4e-0a7d-4d76-bf8f-11b13b0280f1
3bbcee75-cecc-4e26-a1f3-2adf3f2f8c12它们当然唯一,但完全看不出谁先谁后。
如果用 uuidv7,效果会更接近这样:
// index.txt
018f0f7a-7b8e-7f4a-b23e-83b49a8b13a1
018f0f7a-8c21-74d0-9f6d-67db0e7f2e44
018f0f7a-9a10-7065-a6f3-913ca2510d02这里不需要你肉眼精确解码每一位,只要注意一点:它们前面的高位片段会随着时间推进而整体变大。
这会直接带来两个很实用的结果。
第一,数据库索引插入更友好。
因为它们不再像 uuidv4 一样完全随机散落,而是更接近顺序插入。
第二,日志和排查时更直观。
只看 id 的整体趋势,就能粗看出生成先后,不必每次都先查 created_at 才知道大概顺序。
也就是说,uuidv7 的优势不是“人眼可读”,而是“工程系统更好处理”。
6. 一个误区
这里还要专门澄清一个误区:uuidv7 不是数据库自增 id 那种严格单调递增。
原因很简单。
同一毫秒内可能生成很多个 uuidv7,这时它们共享同一个时间前缀,后面的随机位会决定具体值。也就是说:
- 不同毫秒之间,它整体趋势是按时间向前的
- 同一毫秒之内,它不保证严格的自然数递增
但这并不影响它的工程价值。
因为大多数系统真正需要的,从来都不是“绝对数学单调”,而是:
- 大体可排序
- 索引比完全随机更友好
- 不依赖中心节点
- 不容易冲突
uuidv7 恰好把这四件事同时做到了。
7. 使用
实际项目里,一般不会手写位操作,而是直接用现成库,例如:Node.js / TypeScript 里常见的方式会是:
// index.ts
import { uuidv7 } from 'uuidv7'
const userId = uuidv7()
const sessionId = uuidv7()
const refreshTokenId = uuidv7()或者如果你使用的是支持 v7 的 uuid 生态库,也可以直接用对应 API。重点不在具体包名,而在生成结果要满足这几个前提:
- 符合 UUID v7 规范
- 可在当前实例本地生成
- 不需要额外依赖数据库发号
在 Cloudflare Worker 这类运行时里,这种本地生成的模式会非常自然。只要当前实例能拿到时间和随机源,就能直接生成 id,不需要为了“先申请一个主键”再多走一跳。
8. 在项目中引入 uuidv7
确定方案之后,下一步就是把 uuidv7 作为工作区共享依赖引进来,而不是只在某一个子站里临时安装。
当前仓库的依赖策略已经很明确:
- 公共版本统一放在
pnpm-workspace.yaml的catalog - 具体子站各自在
package.json里声明依赖,但版本通过catalog:统一引用
所以接入方式也应该沿着这个约定走。
先在 workspace catalog 里补一条:
// pnpm-workspace.yaml
catalog:
uuidv7: ^1.0.2然后在需要生成 id 的子站里声明依赖。
API 子站肯定要加,因为后面用户 id、session 记录 id、登录挑战记录 id 这类主键,大概率都会在服务端生成:
// apps/api/package.json
{
"dependencies": {
"uuidv7": "catalog:"
}
}web 和 admin 当前阶段也可以一并加上:
// apps/web/package.json
{
"dependencies": {
"uuidv7": "catalog:"
}
}// apps/admin/package.json
{
"dependencies": {
"uuidv7": "catalog:"
}
}9. 数据库索引层面
很多人第一次看 uuidv7 时,最容易忽略它真正的工程价值:索引友好性。
数据库主键如果完全随机,插入位置会在索引树里四处散开,写入局部性很差。随着数据量变大,页分裂、缓存命中和写入稳定性都会受到影响。
而 uuidv7 因为高位带时间,整体趋势更接近递增插入,所以会带来几个很实际的好处:
- 主键插入更接近顺序追加
- 索引页分裂压力比
uuidv4更小 - 按 id 排序时更接近真实创建时间
- 日志和记录仅靠 id 粗看就能判断先后
它当然不等于数据库自增 id 那种完美顺序,但和完全随机的 uuidv4 相比,已经是很明显的提升。
这也是为什么很多团队开始把 uuidv7 当成「现代通用主键」看待,而不是只把它当成又一种 UUID 版本。
10. 在登录和鉴权系统里,哪些对象适合优先用 uuidv7
如果放到后续要做的登录和鉴权方案里,最适合优先考虑 uuidv7 的对象通常包括:
- 用户主键
- session 记录 id
- refresh token 记录 id
- 登录设备记录 id
- 安全审计日志 id
这些对象有个共同点:
- 需要全局唯一
- 往往会跨服务、跨边缘节点创建
- 后面还要长期拿去查库、排日志、做审计
在这些场景里,uuidv7 的收益会比单纯的 uuidv4 更明显。
相反,如果只是临时前端 key、展示短码、营销邀请码,这类场景就不一定非得用 uuidv7