有序性

有序性是分布式 ID 的重要特性之一。不同的有序性级别对系统性能和业务逻辑都有重要影响。

有序性的级别

1. 严格递增(Strictly Increasing)

定义:每个新生成的 ID 都严格大于前一个 ID。

示例

1, 2, 3, 4, 5, 6, 7, 8, 9, 10

特点

  • ✅ 最理想的有序性
  • ✅ 便于范围查询
  • ❌ 需要全局协调
  • ❌ 性能开销大

实现方式

  • 单机计数器
  • 全局分布式锁
  • 中心化 ID 服务

2. 趋势递增(Monotonically Increasing)

定义:ID 大致按时间递增,允许短暂的无序。

示例

1001, 1002, 1003, 1005, 1004, 1006, 1007
           ← 短暂无序

特点

  • ✅ 满足大部分业务需求
  • ✅ 性能较好
  • ✅ 实现相对简单
  • ❌ 非严格有序

实现方式

  • 雪花算法
  • 号段模式
  • 基于时间戳的方案

3. 大致有序(Roughly Ordered)

定义:ID 按时间大致排序,但允许较大的无序范围。

示例

20240410001, 20240410005, 20240410003, 20240410008
                范围内无序

特点

  • ✅ 实现简单
  • ✅ 性能好
  • ❌ 无序范围可能较大
  • ❌ 对范围查询不友好

实现方式

  • 带时间戳的随机 ID
  • 各节点独立生成

4. 无序(Unordered)

定义:ID 完全随机,没有任何顺序。

示例

f47ac10b-58cc-4372-a567-0e02b2c3d479
550e8400-e29b-41d4-a716-446655440000
6ba7b810-9dad-11d1-80b4-00c04fd430c8

特点

  • ✅ 实现最简单
  • ✅ 性能最好
  • ✅ 分布式友好
  • ❌ 完全无序
  • ❌ 对索引不友好

实现方式

  • UUID v4
  • 完全随机的 ID

为什么需要有序性

1. 数据库索引性能

B+ 树索引

MySQL InnoDB 使用 B+ 树索引
有序 ID 有利于:
- 减少页分裂
- 减少页合并
- 提高缓存命中率
- 降低磁盘 I/O

性能对比

有序 ID:
- 插入性能:50000 TPS
- 页分裂次数:少
- 缓存命中率:高

无序 ID:
- 插入性能:10000 TPS
- 页分裂次数:多
- 缓存命中率:低

2. 范围查询效率

查询场景

数据设计要点

  • 查询目标是快速定位状态、任务或资源,避免在关键路径上做大范围扫描。

有序性优势

  • ✅ 可以利用索引
  • ✅ 可以使用 ORDER BY
  • ✅ 可以使用 LIMIT
  • ✅ 可以做分页查询

3. 分页查询友好

分页场景

数据设计要点

  • 查询目标是快速定位状态、任务或资源,避免在关键路径上做大范围扫描。

有序性优势

  • ✅ 排序速度快
  • ✅ OFFSET 性能好
  • ✅ 分页稳定

4. 数据恢复和回溯

恢复场景

系统故障后需要恢复数据:
1. 按订单 ID 范围恢复
2. 按时间戳范围恢复
3. 有序 ID 便于定位

5. 数据分析和统计

分析场景

数据设计要点

  • 查询目标是快速定位状态、任务或资源,避免在关键路径上做大范围扫描。
  • 关键字段包括 SELECT,它们决定后续查询和管理能力。

无序性的应用场景

虽然有序性很有用,但有些场景更适合无序 ID:

1. 安全性要求

场景:不希望泄露业务信息

✅ 随机 ID:用户无法推测用户量
❌ 递增 ID:用户ID = 10000001,泄露用户量

2. 防止遍历攻击

场景:防止恶意遍历所有用户

✅ 随机 ID:无法枚举
❌ 递增 ID:可以从 1 遍历到 N

3. 分布式友好

场景:多节点独立生成,无需协调

✅ 随机 ID:各节点独立生成
❌ 递增 ID:需要协调保证递增

有序性 vs 性能的权衡

权衡表格

有序性级别性能实现复杂度适用场景
严格递增⭐⭐⭐⭐⭐⭐⭐小规模系统
趋势递增⭐⭐⭐⭐⭐⭐⭐大部分场景
大致有序⭐⭐⭐⭐⭐⭐⭐日志系统
无序⭐⭐⭐⭐⭐消息系统

推荐选择

严格递增

  • 订单号(需要可读)
  • 单据号(需要连续)
  • 小规模系统

趋势递增

  • 用户 ID
  • 商品 ID
  • 评论 ID
  • 大部分业务 ID

大致有序

  • 日志 ID
  • 消息 ID
  • 追踪 ID

无序

  • Token
  • 会话 ID
  • 临时 ID

有序性实现技巧

1. 时间戳作为前缀

实现

ID = timestamp(毫秒) + 序列号

示例:
20240410123456789
↑         ↑
时间戳    序列号

优点

  • ✅ 天然有序
  • ✅ 包含时间信息
  • ✅ 实现简单

2. 雪花算法

实现

ID = 时间戳(41位) + 节点ID(10位) + 序列号(12位)

特点:
- 同一毫秒内递增
- 跨毫秒也趋势递增
- 分布式友好

3. 号段模式

实现

从数据库获取号段:[1000, 2000]
本地递增使用:1001, 1002, 1003, ...

特点:
- 号段内严格递增
- 跨号段大致有序
- 性能高

常见错误

错误1:过度追求严格递增

❌ 错误做法:
为了严格递增,使用全局锁,牺牲性能

✅ 正确做法:
追求趋势递增,满足大部分需求

错误2:忽视有序性对性能的影响

❌ 错误做法:
使用 UUID,无序 ID 导致数据库性能差

✅ 正确做法:
使用趋势递增 ID,优化数据库性能

错误3:混用不同有序性的 ID

❌ 错误做法:
同一个表混用严格递增和无序 ID

✅ 正确做法:
保持一致的有序性级别

有序性检查清单

在实现分布式 ID 生成器时,确保:

  • 明确有序性需求级别
  • 理解有序性对数据库性能的影响
  • 权衡有序性和性能
  • 选择合适的有序性级别
  • 验证 ID 的有序性
  • 考虑分布式环境下的有序性
  • 处理时钟回拨对有序性的影响
  • 测试大规模数据下的性能

下一步

了解了有序性后,我们继续学习 ID 的性能要求。

👉 下一节:性能要求