Spring Boot重启后PostgreSQL自增ID跳号问题咨询
我使用Spring Boot结合JPA与PostgreSQL开发应用,发现每次重启服务器后,表中记录的自增ID会跳过50个值。例如,表中最后一条记录ID为50,重启后通过API插入新记录时,ID会被设为100;若不重启服务器,插入记录的ID则会按101、102正常递增。
我的实体类继承自BaseEntity基类,该类通过@GeneratedValue(strategy = GenerationType.AUTO)生成ID,Hibernate DDL配置为仅校验模式。
请问:
- 能否实现服务器重启后自增ID从最后已知ID开始,避免跳号?
- 该问题是否与PostgreSQL的序列机制相关?
BaseEntity代码
@Getter @Setter @MappedSuperclass public abstract class BaseEntity implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id", nullable = false) private Integer id; @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof BaseEntity that)) return false; return id.equals(that.id); } @Override public int hashCode() { return Objects.hash(id); } @Override public String toString() { return "BaseEntity {" + "id = " + id + "}"; } }
BaseEntityAudit代码
@Getter @Setter @MappedSuperclass public abstract class BaseEntityAudit extends BaseEntity implements Serializable { private String createdBy; private String updatedBy; @CreationTimestamp @Column(name = "created_at", updatable = false) private Date createdAt; @UpdateTimestamp @Column(name = "updated_at") private Date updatedAt; @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof BaseEntityAudit)) return false; if (!super.equals(o)) return false; BaseEntityAudit that = (BaseEntityAudit) o; return createdBy.equals(that.createdBy) && updatedBy.equals(that.updatedBy) && createdAt.equals(that.createdAt) && updatedAt.equals(that.updatedAt); } @Override public int hashCode() { return Objects.hash(super.hashCode(), createdBy, updatedBy, createdAt, updatedAt); } @Override public String toString() { return "BaseEntityAudit{" + "createdBy='" + createdBy + '\'' + ", updatedBy='" + updatedBy + '\'' + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + "}" + super.toString(); } }
问题根源
这个问题完全和PostgreSQL的序列机制以及Hibernate的默认配置相关。
当你使用GenerationType.AUTO时,Hibernate在PostgreSQL环境下会默认采用序列(Sequence)生成ID。PostgreSQL的序列默认开启了缓存(cache)功能,默认缓存大小为50——Hibernate会一次性从数据库获取50个ID存入内存,后续插入操作直接使用内存中的ID,避免频繁查询数据库,以此提升性能。
但服务器重启时,内存中未使用的缓存ID会全部丢失。重启后Hibernate会再次向序列请求下一批50个ID,这就导致了ID跳号:比如之前已使用50个ID,缓存中剩余的50个未被使用就丢失,重启后序列直接从100开始分配ID。
实现无跳号的解决方案
方案1:修改现有序列的缓存大小为1
直接在PostgreSQL中修改对应表的序列,将缓存设为1:
ALTER SEQUENCE 你的表名_id_seq CACHE 1;
此配置下,Hibernate每次只能从数据库获取1个ID,重启后不会丢失未使用的ID,但会牺牲部分性能——每次插入都需要查询数据库获取ID。
方案2:在实体类中显式配置序列缓存
在BaseEntity的ID字段上,通过@SequenceGenerator显式指定序列并设置缓存大小为1:
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "base_seq") @SequenceGenerator(name = "base_seq", sequenceName = "base_id_seq", allocationSize = 1) @Column(name = "id", nullable = false) private Integer id;
这里的allocationSize对应PostgreSQL序列的缓存大小,设为1即可避免缓存丢失问题。注意如果数据库中已存在对应序列,需要确保序列的缓存配置也改为1,否则代码配置可能不生效。
方案3:改用IDENTITY生成策略(不推荐高并发场景)
如果你的PostgreSQL版本支持,可以切换到GenerationType.IDENTITY策略,该策略依赖数据库的自增列(如PostgreSQL的SERIAL或BIGSERIAL),不会有缓存相关的跳号问题:
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) private Integer id;
但此方式在高并发场景下性能较差,因为每次插入都需要等待数据库生成ID,无法批量获取ID值。
额外说明
- 多数业务场景下,ID跳号并不会影响功能,反而序列缓存带来的性能提升更有价值。仅当业务对ID连续性有严格要求(如发票编号、流水号)时,才需要调整缓存配置。
- 将缓存设为1后,需注意高并发插入场景下的性能损耗,需根据业务实际情况权衡选择。
内容的提问来源于stack exchange,提问作者rafaelpadu

