Oracle序列在Tomcat重启时按缓存大小+LAST_NUMBER更新的问题咨询
为啥会出现这种跳变情况?
这事儿本质是Oracle序列的缓存机制和应用重启撞在一起导致的:
- 当你的Oracle序列设置了
CACHE 100时,Oracle会提前在内存里存好100个序列值。第一次调用xxx.nextval的时候,Oracle会把LAST_NUMBER更新成当前值加缓存大小(也就是1+100=101),然后把1到100这批值分给应用用。 - 可如果Tomcat重启的时候,应用还没把这100个缓存值用完(比如只用了3个,到3),那剩下的4到100这些值就直接浪费了——因为Tomcat重启后,旧的连接池、Hibernate的上下文全清掉了,之前预取的序列值也跟着没了。
- 等Tomcat重启完,应用再调用
xxx.nextval的时候,Oracle会以为之前那批缓存已经被用完了(其实是应用没用到但丢了),于是直接分配下一批缓存:把LAST_NUMBER更成101+100=201,然后从101开始返回值,这不就跳变了嘛。
另外,如果Hibernate JPA的序列生成配置和Oracle序列的缓存参数不匹配,还会让这个问题更严重——比如Hibernate的allocationSize和序列的CACHE值不一样,可能会导致频繁重复预取序列值。
怎么解决序列跳变的问题?
根据你的场景,有几个靠谱的方案可选:
1. 给序列关掉缓存(NOCACHE)
直接修改Oracle序列,禁用缓存机制:
ALTER SEQUENCE xxx NOCACHE;
这样每次调用nextval都会直接去数据库读并更新LAST_NUMBER,不会提前存缓存值,自然就不会因为应用重启丢值跳变了。缺点就是每次拿序列值都要和数据库交互,性能会有一丢丢下降,适合对序列连续性要求高、并发量不是特别大的场景。
2. 让Hibernate配置和Oracle序列缓存完全对齐
要是你想保留缓存提升性能,就得确保Hibernate的序列生成配置和Oracle序列的参数完全匹配:
- 先把Oracle序列的
CACHE值设为N(比如你的100),同时保证序列的INCREMENT BY是1(默认就是1,不用改)。 - 然后在JPA实体类里,把
@SequenceGenerator的allocationSize设成和序列CACHE一样的值,initialValue和序列初始值保持一致:
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "xxx_seq") @SequenceGenerator(name = "xxx_seq", sequenceName = "XXX", allocationSize = 100, initialValue = 1) private Long id;
这样Hibernate会跟着Oracle序列的缓存批次来拿值,避免重复预取导致的跳变。另外,Tomcat连接池重启时,Hibernate也不会乱初始化序列生成器的上下文,只要配置对齐,这个问题就能得到缓解。
3. 给序列加ORDER属性(适合RAC集群)
如果你的Oracle是RAC集群环境,还可以给序列加上ORDER属性:
ALTER SEQUENCE xxx CACHE 100 ORDER;
ORDER能保证序列值按请求顺序分配,不会因为集群节点间的缓存导致乱序,但它不能直接解决应用重启的跳变问题,得和上面的缓存对齐方案一起用才管用。
4. 换成IDENTITY自增策略(Oracle 12c及以上可用)
要是你的Oracle版本是12c或更高,可以改用IDENTITY列自增,替代序列:
CREATE TABLE your_table ( id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, -- 其他字段 );
然后在JPA里这么配置:
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
IDENTITY是数据库层面的自增,不会有缓存导致的跳变问题,不过高并发场景下,它的性能可能不如带缓存的序列。
内容的提问来源于stack exchange,提问作者Dhruv Raj Singh

