2010-09-02 74 views
3

我使用Hibernate和Joined-SubClasses将类层次结构映射到 数据库。不幸的是,当一个对象被更新而另一个线程试图加载相同的对象时,这会导致死锁。使用将 映射到单个表的对象,这不成问题。这似乎是MSSQL 在类层次结构的表上获取锁的方式造成的。Hibernate与MSSQL加入子类死锁

当Hibernate从数据库加载一个对象时,它使用SELECT与JOIN:

SELECT ... 
FROM 
    subclass 
    LEFT JOIN class 
     ON ... 
WHERE ... 

当Hibernate更新该子类它的一个目的:

UPDATE 
    class 
SET ... 
WHERE ... 

UPDATE 
    subclass 
SET ... 
WHERE ... 

的问题是,如果一个对象在两个更新语句之间加载,它会导致死锁。 SELECT语句似乎锁定了另一个 之后的两个表。那么,什么似乎发生的是:

  1. 线程1个负载的目标和地方共享的两个表
  2. 线程1执行在类表中的UPDATE语句锁和升级的类表 锁独占锁。
  3. 线程2试图通过执行SELECT语句加载同一个对象,它 地方上的子类表的共享锁,然后等待直到在类表中的排他 锁被释放
  4. 线程1执行UPDATE语句对于子类表,它希望 将其在子类表上的锁升级为排它锁,但 表已被线程2锁定,该线程正在等待线程1
  5. 线程2由于死锁而中止线1

死锁图看起来像这样:Deadlock Graph

这些对象经常更新安静,并且这会导致始终造成死锁,甚至在加载单个对象时甚至会导致 。我也尝试用 HSQLDB重现问题,但是它不会死锁,HSQLDB似乎要么将两个表都锁定为 一次,要么等待它可以同时锁定两个表,所以这似乎是一个问题,只有 发生在MSSQL中。

如果不修改 模式(索引除外),用Hibernate避免这个问题的解决方案是什么?

+0

我将Hibernate放在一边问你的问题,这实际上是一个比Hibernate问题更多的MSSQL问题(如果适当标记它,你可能会获得更多的牵引力)。无论如何你正在使用什么事务隔离? PS:甚至不尝试重现HSQLDB的问题(不确定使用的是哪个版本,但HSQLDB 1.8.2仅支持READ_UNCOMMITTED隔离级别,因此情况有很大不同)。 – 2010-09-02 15:02:24

+0

也许,但加载一个对象的事实在两个表上创建了一个连接,并且对象的更新创建了2个独立的UPDATE语句来自Hibernate。如果我离开Hibernate,那么解决方案可能是在单独的事务中运行这两个更新,或者更改SELECT语句。但这是我不能做的事情,因为这些语句是由Hibernate生成的。 我首先使用HSQLDB 1.8.x进行了测试,注意到它不起作用,然后切换到2.0,这延迟了线程2的SELECT语句,直到线程1的事务被提交为止。 – Reboot 2010-09-02 15:45:55

+0

陈述的产生与否与事实无关,这是我的观点。只要提出一个关于**这个**特定场景的问题(顺便说一句,在单独的查询中运行更新不会)。但随时忽略我的建议:) – 2010-09-02 19:50:17

回答

0

在我看来,这些更新需要在单个事务中以原子方式完成。不幸的是,我没有很多关于Hibernate的背景知识,所以我会把它留给其他人来指出你在那里的正确方向。

+0

它们在一次交易中执行。如果不是,他们不会导致其他交易陷入僵局。两个线程的活动都在一个事务中完成。也许我应该更清楚一点。 – Reboot 2010-09-02 12:43:36

1

你打开了SQL Server deadlock trace flags 1204 or 1222?这将有助于准确识别造成死锁的资源。有关更多信息,请参阅Detecting and Ending Deadlocks上的MSDN文章。

这些表上有索引吗?如果是这样,如果应用程序获取聚集索引上的锁,则会发生死锁,然后尝试通过查找非聚集索引来获取同一个表上的更多锁。

+1

我已经使用SQL Management Studio Profiler创建了一个死锁图并将其添加到问题的图形中。在这两个表上只有一行被访问,对我来说,看起来select语句按照与UPDATE语句相反的顺序锁定两个表。每个表中只有一行被访问。我不知道索引如何帮助改变这一点。 – Reboot 2010-09-03 08:15:33