1. 引言

本文将深入解析 Hi/Lo 算法,它是一种常用于数据库主键生成的策略,尤其在 ORM 框架(如 Hibernate)中被广泛采用。

我们会先介绍算法的核心思想,再通过 Hibernate 的实际示例演示其工作流程,最后分析它的适用场景、优点与潜在“踩坑”点。

目标是让你不仅“知道它是什么”,还能在项目中判断:✅ 什么时候该用,❌ 什么时候该避坑。


2. Hi/Lo 算法概述

2.1 基本概念

Hi/Lo 算法的核心目标是:在不频繁访问数据库的前提下,安全地生成唯一的主键 ID

它通过三个关键变量协作完成:

  • incrementSize:每批次可生成的 ID 数量,相当于“步长”。这个值一旦设定应保持不变,否则多实例环境下容易冲突。
  • high:来自数据库序列(sequence)的值,保证全局唯一。
  • low:当前批次内的递增计数器,范围是 [0, incrementSize)

最终生成的 ID 公式为:
(high - 1) * incrementSize + low + 1

生成范围是:[(high - 1) * incrementSize + 1, high * incrementSize)

举个例子,如果 high=2incrementSize=3,那么可用 ID 范围是 [4, 6],即 4、5、6。

⚠️ 关键点:只有当 low >= incrementSize 时,才会去数据库取下一个 high 值。这意味着大部分 ID 都是在内存中生成的,减少了数据库压力。


2.2 算法伪代码

生成下一个 ID 的步骤如下:

  • 如果 low >= incrementSize
    • 从数据库获取新的 high
    • 重置 low = 0
  • 计算 ID:(high - 1) * incrementSize + low + 1
  • low++
  • 返回 ID

简单粗暴地说:先“批发”一批号段,再“零售”分配


3. 实战示例(基于 Hibernate)

我们以 Hibernate 为例,看看 Hi/Lo 是如何配置和工作的。

实体定义

@Entity
public class RestaurantOrder {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "hilo_sequence_generator")
    @GenericGenerator(
      name = "hilo_sequence_generator",
      strategy = "sequence",
      parameters = {
        @Parameter(name = "sequence_name", value = "hilo_sequence"),
        @Parameter(name = "initial_value", value = "1"),
        @Parameter(name = "increment_size", value = "3"),
        @Parameter(name = "optimizer", value = "hilo")
      }
    )
    private Long id;
}

📌 关键配置说明:

参数 说明
strategy = "sequence" 使用数据库序列
optimizer = "hilo" 启用 Hi/Lo 优化器
increment_size = 3 每次“批发”3个ID

持久化操作

public void persist() {
    Transaction transaction = session.beginTransaction();

    for (int i = 0; i < 9; i++) {
        session.persist(new RestaurantOrder());
        session.flush(); // 强制触发ID生成
    }

    transaction.commit();
}

我们插入 9 条记录,incrementSize=3,理论上只需要 3 次数据库调用来获取 high 值。


日志输出验证

Hibernate: call next value for hilo_sequence
org.hibernate.id.enhanced.SequenceStructure  - Sequence value obtained: 1
org.hibernate.event.internal.AbstractSaveEventListener  - Generated identifier: 1
org.hibernate.event.internal.AbstractSaveEventListener  - Generated identifier: 2
org.hibernate.event.internal.AbstractSaveEventListener  - Generated identifier: 3

Hibernate: call next value for hilo_sequence
org.hibernate.id.enhanced.SequenceStructure  - Sequence value obtained: 2
org.hibernate.event.internal.AbstractSaveEventListener  - Generated identifier: 4
org.hibernate.event.internal.AbstractSaveEventListener  - Generated identifier: 5
org.hibernate.event.internal.AbstractSaveEventListener  - Generated identifier: 6

Hibernate: call next value for hilo_sequence
org.hibernate.id.enhanced.SequenceStructure  - Sequence value obtained: 3
org.hibernate.event.internal.AbstractSaveEventListener  - Generated identifier: 7
org.hibernate.event.internal.AbstractSaveEventListener  - Generated identifier: 8
org.hibernate.event.internal.AbstractSaveEventListener  - Generated identifier: 9

✅ 完美印证:

  • 第一次取 high=1 → ID: 1~3
  • 第二次取 high=2 → ID: 4~6
  • 第三次取 high=3 → ID: 7~9

整个过程仅 3 次数据库交互,高效且可控。


4. 优缺点分析

✅ 优点

  • 减少数据库往返次数incrementSize 越大,性能越高,尤其适合高并发场景。
  • 适合弱网络环境:应用本地生成 ID,对数据库依赖低。
  • ID 趋于连续:相比 UUID,更利于数据库索引优化(B+树插入效率更高)。

❌ 缺点(踩坑预警)

  • ID 不连续且有空洞:如果应用重启,当前批次未用完的 ID 会丢失,造成“断号”。
  • 多系统写入冲突风险:如果有其他服务(如脚本、第三方系统)直接往同一表插数据,并使用了自增或随机 ID,可能撞上 Hi/Lo 当前占用的号段,导致主键冲突。
  • 难以预测下一个 ID:不适合需要“预知 ID”的业务场景(比如生成订单号前缀)。

📌 建议使用场景:

  • 单一应用写入
  • 高并发、低延迟要求
  • ID 只用于主键,不参与业务逻辑

🚫 不建议使用场景:

  • 多个异构系统共写一张表
  • 需要严格连续 ID(如财务流水号)
  • 分布式 ID 已有统一方案(如 Snowflake)

5. 总结

Hi/Lo 算法是一种经典的主键生成策略,通过“号段 + 内存分配”的方式,显著降低数据库压力,提升插入性能。

在 Hibernate 中配置简单,只需设置 optimizer = "hilo" 和合适的 increment_size 即可。

但也要注意它的局限性,特别是在多系统协作或对 ID 连续性有要求的场景下,容易“踩坑”。

示例代码已整理至 GitHub:https://github.com/baeldung/tutorials/tree/master/persistence-modules/hibernate5


原始标题:What Is the Hi/Lo Algorithm?