我正在寻找一种在Java 6应用程序中处理数据库死锁的好策略;可能会有几个并行线程同时写入同一个表。如果数据库(Ingres RDMBS)检测到死锁,它将随机终止其中一个会话。
考虑到以下要求,处理死锁情况的可接受技术是什么?
到目前为止,我提出的策略是这样的:
short attempts = 0;
boolean success = false;
long delayMs = 0;
Random random = new Random();
do {
try {
//insert loads of records in table 'x'
success = true;
} catch (ConcurrencyFailureException e) {
attempts++;
success = false;
delayMs = 1000*attempts+random.nextInt(1000*attempts);
try {
Thread.sleep(delayMs);
} catch (InterruptedException ie) {
}
}
} while (!success);
可以以任何方式改进吗?例如等待固定数量(幻数)秒。 是否有不同的策略可以产生更好的结果?
注意:将使用几种数据库级技术来确保实际上非常罕见的死锁。此外,应用程序将尝试避免调度同时写入同一个表的线程。上述情况只是“最糟糕的情况”。
注意:插入记录的表被组织为堆分区表并且没有索引;每个线程都会在其自己的分区中插入记录。
答案 0 :(得分:10)
常用的方法是某种形式的指数退避。而不是你的1000*attempts+random
方法,使延迟成为尝试次数的指数函数。这可以确保在前一次或两次尝试中的最小延迟,在这种情况下,您可能只是运气不好而导致死锁,但在以后很明显连接确实很拥挤时会给您带来更大的延迟。
当然,另一种方法是尝试安排数据库访问,以便不太可能发生死锁。但是,如果不知道你的查询是什么(以及如何以及何时执行),就无法确定是否可以做到这一点
答案 1 :(得分:1)
这就是我们这样做的方式。循环并重试事务直到完成。
我们并没有随意延迟。
另外,我们在try
块内部进行了提交,并在异常处理程序中进行了回滚。
当您有多个可锁定资源和多个并发事务时,死锁是不可避免的。这是锁定争用的合理结果。
如果你避免争用锁(即悲观的表级锁定),那么你也倾向于阻止并发。如果您可以定义不争用锁的事务,则可以避免死锁。但是,对同一个表的并发访问几乎就是死锁的定义。
加载时,插入(特别是在HEAP表中)可以(通常)并行进行而不会出现很多争用问题。如果您延迟构建索引,则插入期间不会进行其他更新。
因此,您可以通过删除索引,将组织更改为堆,加载多个并发进程(或线程,通常更快地拥有多个进程)来避免,然后构建索引(并可能重新组织表),你可以避免死锁。
进行更新或删除时,帮助不大。
答案 2 :(得分:1)
如果您不需要同时访问数据库,则一个简单的解决方案可能是删除它并使用任务处理队列来更新数据库,通过队列序列化对数据库的访问。我意识到这将为您的应用程序引入一个异步元素,因此不适合大多数用户启动的应用程序或在线Web应用程序,但可能值得考虑批量/离线类型的应用程序(我意识到可能不是你看的答案)虽然)。
答案 3 :(得分:0)
使用像Ingres这样的数据库,你总会遇到一些死锁,所以你必须假设任何插入,更新或删除都会失败并且有适当的重试策略(如你的例子)。 您应该设计数据库,以便最大限度地减少争用,并且很少发生死锁。如果您在多次重试后不断获得事务失败,那么这表明您将不得不重新设计一些主要数据库(或者转移到像Oracle这样的系统,通常可以设计应用程序以通过适当的使用来避免死锁行级锁定)。
答案 4 :(得分:0)
short attempts = 0;
boolean success = false;
long delayMs = 0;
Random random = new Random();
do {
try {
synchronized(ClassName.class) {
//insert loads of records in table 'x'
}
success = true;
} catch (ConcurrencyFailureException e) {
attempts++;
success = false;
delayMs = 1000*attempts+random.nextInt(1000*attempts);
try {
Thread.sleep(delayMs);
} catch (InterruptedException ie) {
}
}
} while (!success);