Postgresql中使用JPA 2序列生成器时出现奇怪的ID值

6
实体在id列上有以下注释:
@Id
@SequenceGenerator(name = "JOB_MISFIRE_ID_GENERATOR", sequenceName="job_misfire_sequence", allocationSize=10)
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator = "JOB_MISFIRE_ID_GENERATOR")
@Column(unique = true, nullable = false)
private Long id;

在数据库中,我有以下内容:
CREATE SEQUENCE job_misfire_sequence
  INCREMENT 10
  MINVALUE 1
  MAXVALUE 9223372036854775807
  START 1
  CACHE 1;

序列用于获取列的默认值。

ALTER TABLE job_misfires
ALTER COLUMN id SET DEFAULT nextval('job_misfire_sequence');

当我使用nextval('job_misfire_sequence')手动向数据库插入数据时,一切都很顺利。当序列的当前值为1时,生成了以下id值:
 SELECT nextval('job_misfire_sequence'); --> 1
 SELECT nextval('job_misfire_sequence'); --> 11
 SELECT nextval('job_misfire_sequence'); --> 21
 SELECT nextval('job_misfire_sequence'); --> 31

但是,当Hibernate向该表插入一行时,它会从该序列中获取下一个值(在这种情况下为41),并将其乘以10,然后将其用作id值。这意味着插入的行现在具有id值410。

我做错了什么?这种情况将导致冲突,因为Hibernate没有使用序列提供的值。如果我理解正确,则注释中的allocationSize = 10和序列中的INCREMENT 10的组合应确保Hibernate只需要每十个值向序列请求一个新值。为什么不会发生这种情况?为什么要将序列中的值乘以10?

我正在使用:

  • Postgresql 9.0.3
  • Hibernate 3.5.5
  • Hibernate JPA 2.0 api 1.0.0 final

更新1:

正如互联网上建议的那样,在注释中将allocationSize值设置为1可以解决此问题。现在,id值确实是从数据库中的序列中获取的,我可以安全地在该表中手动插入行。

但是:

  • allocationSize=1会导致性能问题吗?
  • 序列中的值不被Hibernate直接使用而是乘以allocationSize值,这不是一个巨大的bug吗?
  • 谁应该为此负责?Hibernate?
  • 是否有可用的修复程序?

在https://dev59.com/E2cs5IYBdhLWcg3wjk0Y上找到正确的解决方案。 - Iker Jimenez
4个回答

8

看起来正确的方法是以下这样:

@Id
@SequenceGenerator(name = "JOB_MISFIRE_ID_GENERATOR", sequenceName="job_misfire_sequence", allocationSize=1)
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator = "JOB_MISFIRE_ID_GENERATOR")
@Column(unique = true, nullable = false)
private Long id;

在数据库中,我有以下内容:
CREATE SEQUENCE job_misfire_sequence
  INCREMENT 1
  MINVALUE 1
  MAXVALUE 9223372036854775807
  START 1
  CACHE 10;

现在,当我手动使用nextval('job_misfire_sequence')将内容插入数据库时,一切都按预期工作。当序列的当前值为1时,会生成以下id值:
 SELECT nextval('job_misfire_sequence'); --> 1
 SELECT nextval('job_misfire_sequence'); --> 2
 SELECT nextval('job_misfire_sequence'); --> 3
 SELECT nextval('job_misfire_sequence'); --> 4

现在Hibernate的工作方式也符合我的预期。当我在一个会话中插入了4行记录后,如果它在此之后再插入一行记录,那么返回给Hibernate的序列值就是11。Hibernate将其用作第一条记录的ID值、下一条记录为11,以此类推。因为我在数据库中设置了CACHE选项的值为10,Hibernate现在只需要调用一次序列,然后就可以使用10个连续的ID值。我确认这确实是真实情况,并且ID值不会重叠。
所以,关键点如下:
  • 必须在注释中使用allocationSize=1
如果您想优化数据库插入的性能,请使用:
  • 在数据库中使用大于1的CACHE设置值,但不要更改allocationSize。
为了获得漂亮的连续ID值:
  • 你必须使用INCREMENT 1。

谢谢,将“allocationSize”更改为1对我有帮助!之前id被乘以50。哈哈 - Jaanus
这不是正确的。使用allocationSize=1,每次都会访问数据库。你需要的是hibernate.id.new_generator_mappings=true。请参见https://dev59.com/E2cs5IYBdhLWcg3wjk0Y。 - Iker Jimenez

0
据我理解,nextVal('job_misfire_sequence');将返回下一个SEQUENCE值,因此您需要的值。Hibernate可以从中抽象出来,并假定从数据库返回的值是正确的。因此,您不需要allocationSize=10,因为数据库已经返回了正确的值。

是的,Hibernate 绝对应该期望从数据库获得正确的值。但是 allocationSize 的文档说:“分配序列号时要增加的数量。”它没有提到乘法。使用 allocationSize 100 和 INCREMENT 100 应该使 Hibernate 表现得好像它只需要一次请求序列值,然后就可以安全地为下一个 100 条记录分配 ID 值。至少我是这么理解的。 - kosoant
我知道这可能有点啰嗦,但您是否对Hibernate在创建每个新对象时发送查询以获取下一个序列号的问题感到困扰?这是我的代码目前的工作方式... 作为基准:在单独的查询中获取约650000个序列号大约需要1分钟。 - iliaden
知道这点很好。所以这个“优化”很可能完全没有必要。 - kosoant

0

我不熟悉Hibernate,但是当在PostgreSQL中使用缓存值创建序列时,该缓存是基于每个连接的。这意味着如果您从不同的会话(=连接)调用nextval(),您也可能看到此行为。

来自手册的引用:

如果将大于1的缓存设置用于将由多个会话并发使用的序列对象,则可能会获得意外结果。每个会话将在访问序列对象期间分配和缓存连续的序列值,并相应地增加序列对象的last_value。然后,在该会话中的下一个缓存-1次使用nextval时,仅返回预先分配的值而不接触序列对象。因此,在会话中分配但未使用的任何数字将在该会话结束时丢失,导致序列中出现“空洞”

请确保阅读手册中的“注释”部分: http://www.postgresql.org/docs/current/static/sql-createsequence.html


缓存设置在我的序列中实际上没有被使用。 - kosoant
手册中提到的意外结果是关于生成的ID值之间的间隙。这并不是问题。重叠的ID值才是问题,但使用CACHE>1不会导致这种情况发生。 - kosoant

0

这就是它的工作原理。

使用序列生成器和allocationSize时,Hibernate从序列中获取一个数字来生成allocationSize个标识符。因此,在从序列中获取值N之后,它会从allocationSize * NallocationSize * (N + 1) - 1生成标识符。然后,它获取序列中的下一个值以生成下一批标识符。如果下一个值是N + 1,则生成的标识符是连续的,因此它期望产生连续数字的序列。

因此,在序列定义中不需要指定increment


allocationSize的文档说明:“从序列中分配序列号时要增加的数量。”它没有提到任何关于乘法的内容。但这不是问题所在。问题在于Hibernate或我最终会尝试插入具有冲突ID值的行到该表中。这不可能是预期的工作方式。那么更改数据库序列的INCREMENT值有什么帮助呢? - kosoant
@kosoant:如果你想从单个序列值中产生多个标识符,你必须使用乘法,你明白吗? - axtavt
不,我不明白为什么必须使用乘法来从单个nextval('sequence')查询中获取多个id值。对于单个值,您必须使用乘法,但这不是我问题的重点。正如您可以从我自己对此问题的回答中看到的那样,“从单个序列查询生成多个标识符”的非乘法方法实际上是可能的。 - kosoant

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接