通过在事务中包装几乎所有内容并注意创建的代码没有可能出现死锁的代码(真正了解数据模型,写出流程流),它们可以防止死锁。 InnoDB 使用当前的页面锁定方式往往很容易死锁。
另外我认为他们不会将 InnoDB 用于任何严重的代码,因为这甚至会导致页面锁死锁。 Oracle 和 MS SQL Server 等较大的商业数据库使用页锁和行锁以更细粒度的方式处理此问题。此外,例如 Oracle 可以通过控制页面填充和空百分比来更好地控制,这减少了页面锁阻塞事务的机会(不是死锁,只是阻塞一段时间)。
对于hibernate 或任何其他代码:一旦遇到死锁情况,通常需要改进代码。这个想法是您可以控制重新启动事务(在某些情况下给您一个死锁循环:死锁、重新启动和再次死锁等)。这种代码可以很简单:
- 发生死锁异常;
- 只在初始调用函数中捕获死锁异常;
- 使用相同的参数再次调用初始调用函数。
InnoDB 甚至单条记录事务死锁的事务量也很高。这不应该发生,并且是 MySQL(和衍生问题)。如果您正在解决这个技术问题,那就是彩票:您可以通过更改 ACID 合规性或更改技术/逻辑表布局(例如通过分区)来增加机会。分区将减少 DBMS 更新索引所花费的时间,从而缩短事务时间,从而减少由于记录锁升级而导致死锁的机会。将页面大小减小到较小的页面大小具有类似的效果。然而:它只是减少了机会(所以你会看到更少的死锁,但它们仍然会发生)。
有两种可能的解决方案可以完全避免死锁:
- 重写代码,以便单例处理发生死锁的表。必须对代码进行分区,以便在此表上执行操作的代码在单个事务中,而所有其他代码在其他事务中。这会破坏任何事务设计,如果发生另一个问题,回滚事务(现在至少是 2 个甚至可能是 3 个事务)将是一场噩梦。
- 切换 DBMS 引擎:如果您使用 hibernate,这非常简单,只是您必须学会处理这个新 DBMS 的内部结构。
使用 hibernate/Spring/JPA @Transaction 处理死锁:
使用单向流的单例设计:
函数 A 调用函数 B、C、D,但 B、C、D 不会向 A 返回任何数据,除了确认或密钥(如果可能)。
Hibernate 会在发生异常时回滚缓存上的更改,因此主要担心的是调用函数。只要是无状态的,最重要的是事务启动函数A:
一些用户通过操作调用控制器。这个动作调用了函数A:
public String someControllerAction(...) {
try {
... some work, should be minimal else we have to undo this in the catch
saveMyData(...);
} catch(TransactionException exp) {
... undo some work
/* This is a loop, so it can get stuck. You can keep a loop
counter or another check to prevent getting stuck forever.
You can also throw this back to the user with a **please retry
button** */
someControllerAction(...);
}
// Starting transaction so that it can be restarted.
@Transactional
private saveMyData(...) throws TransactionException {
try {
... some work
} catch(TransactionException exp) {
... some roll back work if required
Throw new TransactionException();
}
}
数据访问层抛出的异常必须在@Transaction之外捕获,这样所有的