如果没有行匹配,则UPDATE不会锁定表,竞争条件

时间:2012-08-21 09:06:43

标签: sql oracle transactions locking

我想从数据库表中递增并返回一个计数器。

java代码如下:

String sqlUpdate = "UPDATE mytable SET col3 = col3 + 1 WHERE colpk1 = ? AND colpk2 = ?";
Query queryUpdate = manager.createNativeQuery(sqlUpdate);
queryUpdate.setParameter(1, ...);
queryUpdate.setParameter(2, ...);

int num = queryUpdate.executeUpdate();

if (num == 0) {
    long count = 1;
    String sqlInsert = "INSERT INTO mytable (colpk1, colpk2, col3) VALUES (?,?,?)";
    Query queryInsert = manager.createNativeQuery(sqlInsert);
    queryInsert.setParameter(1, ...);
    queryInsert.setParameter(2, ...);
    queryInsert.setParameter(3, count);
    queryInsert.executeUpdate();

    return count;
} else {
    String sqlSelect = "SELECT col3 FROM mytable WHERE colpk1 = ? AND colpk2 = ?";
    Query querySelect = manager.createNativeQuery(sqlSelect);
    querySelect.setParameter(1, ...);
    querySelect.setParameter(2, ...);

    Object result = querySelect.getSingleResult();

    return Long.parseLong(result.toString());
}

如果已经存在具有给定主键的行,这也可以同时使用(创建锁)。但是,如果该行尚不存在(num == 0),则UPDATE不会锁定,并且在两个查询之间可能发生并发访问,然后在执行INSERT作为新行时导致Unique Constraint验证在此期间已经创建了。

解决此问题的最佳方法是什么?首先使用SELECT FOR UPDATE然后根据执行UPDATE或INSERT的结果会更好吗?

2 个答案:

答案 0 :(得分:1)

MERGE语句将避免拆分语句。

http://en.wikipedia.org/wiki/Merge_(SQL

或者,您可以始终为条件发生时的罕见情况捕获唯一约束异常,然后重试。

答案 1 :(得分:0)

由于Merge可以在并发执行中抛出Unique Constraint异常,因此最好的解决方案是在执行插入时捕获异常,然后行必须在那里,然后继续更新。

在容器管理事务的情况下提交此事务是下一个问题,因为例外导致isRollBackOnly == true。可行的方法是使用新的bean调用在新事务中尝试插入,请参阅Commit transaction after exception - undo setRollbackOnly