情景很简单 我有一个包含两个表的大型MySQL数据库:
-- Table 1
id (primary key) | some other columns without constraints
-----------------+--------------------------------------
1 | foo
2 | bar
3 | foobar
... | ...
-- Table 2
id_src | id_trg | some other columns without constraints
-------+--------+---------------------------------------
1 | 2 | ...
1 | 3 | ...
2 | 1 | ...
2 | 3 | ...
2 | 5 | ...
...
id
是主键。该表包含大约12M条目。id_src
和id_trg
都是主键,并且在table1的id
上都有外键约束,并且还启用了选项DELETE ON CASCADE
。该表包含大约110M条目。好的,现在我正在做的只是创建一个我要从表1中删除的id
列表,然后我正在执行一个简单的DELETE FROM table1 WHERE id IN (<the list of ids>);
后一个过程就像你猜测的那样也会从table2中删除相应的id。到目前为止一切顺利,但问题是当我在多线程环境中运行它时,我得到了很多Deadlocks
!
一些注释:
我已经尝试了SELECT ... FOR UPDATE
,但简单的DELETE
最多需要30秒! (所以没有使用它):
DELETE FROM table1
WHERE id IN (
SELECT id FROM (
SELECT * FROM table1
WHERE id='some_id'
FOR UPDATE) AS x);
我该如何解决这个问题?
我希望得到任何帮助和提前感谢:)
修改:
java.util.concurrent
SELECT
的说明,请参阅MySQL can’t specify target table for update in FROM clause FOR UPDATE
条款是我听说它锁定了一行而不是锁定整个表DELETE ON CASCADE
id_src
和id_trg
(每个单独)意味着table1 id=x
上的每次删除都会导致table2 id_src=x
和{{}上的删除1}}
根据要求提供了一些代码:
id_trg=x
和public void write(List data){
try{
Arraylist idsToDelete = getIdsToDelete();
String query = "DELETE FROM table1 WHERE id IN ("+ idsToDelete + " )";
mysqlJdbcTemplate.getJdbcTemplate().batchUpdate(query);
} catch (Exception e) {
LOG.error(e);
}
}
只是一个扩展myJdbcTemplate
的抽象类。
答案 0 :(得分:3)
首先,你传递id的第一个简单删除查询,如果你传递id到1000之类的限制,就不会产生问题(子表中的总行数也应该接近但不是很多像10,000等),但如果您传递的数量超过50,000或更多,则会产生锁定问题。
为避免死锁,您可以按照以下方法处理此问题(假设批量删除不属于生产系统) -
步骤1:通过选择查询获取所有ID并保留在游标中。
步骤2:现在逐个删除存储在游标中的这些ID。
注意:要检查删除获取锁定的原因,我们必须检查一些事项,例如您传递的ID数量,数据库级别设置的事务级别,my.cnf中的Mysql配置设置等等。 / p>
答案 1 :(得分:3)
删除许多(> 10000)父记录可能是危险的,每个父记录都有通过级联删除的子记录,因为您一次删除的记录最多,锁定冲突的可能性最大,导致死锁或回滚。
如果可以接受(意味着您可以与数据库建立直接的JDBC连接),则应该(此处不涉及线程):
由于较重的工作应该放在数据库部分,我几乎不怀疑线程在这里会有所帮助。如果您想尝试一下,我建议:
DELETE FROM table1 WHERE id = ?
我无法想象整个过程可能需要几分钟。
经过其他一些阅读后,看起来我已经习惯了旧系统,而且我的数字非常保守。
答案 2 :(得分:0)
好的,这就是我所做的,它可能实际上并没有避免死锁,而是我唯一的选择。
此解决方案实际上是一种使用Spring 处理 MySQL死锁的方法。
抓住并重试死锁:
public void write(List data){
try{
Arraylist idsToDelete = getIdsToDelete();
String query = "DELETE FROM table1 WHERE id IN ("+ idsToDelete + " )";
try {
mysqlJdbcTemplate.getJdbcTemplate().batchUpdate(query);
} catch (org.springframework.dao.DeadlockLoserDataAccessException e) {
LOG.info("Caught DEADLOCK : " + e);
retryDeadlock(query); // Retry them!
}
} catch (Exception e) {
LOG.error(e);
}
}
public void retryDeadlock(final String[] sqlQuery) {
RetryTemplate template = new RetryTemplate();
TimeoutRetryPolicy policy = new TimeoutRetryPolicy();
policy.setTimeout(30000L);
template.setRetryPolicy(policy);
try {
template.execute(new RetryCallback<int[]>() {
public int[] doWithRetry(RetryContext context) {
LOG.info("Retrying DEADLOCK " + context);
return mysqlJdbcTemplate.getJdbcTemplate().batchUpdate(sqlQuery);
}
});
} catch (Exception e1) {
e1.printStackTrace();
}
}
答案 3 :(得分:0)
另一种解决方案可能是使用Spring的多个步骤机制 这样DELETE查询被拆分为3,因此通过删除阻塞列开始第一步,其他步骤分别删除其他两列。
Step1: Delete id_trg from child table;
Step2: Delete id_src from child table;
Step3: Delete id from parent table;
当然最后两个步骤可以合并为1,但在这种情况下需要两个不同的ItemsWriters!