实现计数器JDBC并发问题

时间:2013-01-10 14:29:08

标签: java mysql jdbc concurrency transactions

我正在尝试在JDBC中实现一个计数器,该方法实际上增加了一个列值并返回了新的列值,下面是我的代码的片段我首先执行select ... for update来获取旧值(也得到结果)在行锁中)然后在该行上执行更新。我是否正确地假设没有其他事务可以干扰select .. for update和update之间的列val的值。

        //get connection here
        con.setAutoCommit(false);
        PreparedStatement statement = con.prepareStatement("SELECT val FROM atable WHERE id=? FOR UPDATE");
        statement.setInt(1, id);
        ResultSet resultSet = statement.executeQuery();
        if (resultSet.next()) {
            oldVal = resultSet.getInt("val");
            statement = con.prepareStatement("UPDATE atable SET val=" + oldVal + "+1 WHERE id=?");
            statement.setInt(1, id);
            int result = statement.executeUpdate();
            if (result != 1) {
                throw new SQLException("error message");
            } else {
                newVal = oldVal + 1;//could do another select statement to get the new value

                statement.close();

            }
        } else {
                throw new SQLException("error message");
        }
        resultSet.close();
        con.commit();
        con.setAutoCommit(true);
       //close connection here

感谢。

4 个答案:

答案 0 :(得分:0)

我会做类似下面的代码。 (我拿出了可读性的检查和例外;随意添加它们。)

con.setAutoCommit(false);
PreparedStatement incrementVal = 
    con.prepareStatement("UPDATE atable SET val = val + 1 WHERE id=?");
incrementVal.setInt(1, id);
statement.executeUpdate();
PreparedStatement selectIncrementedVal = 
    con.prepareStatement("SELECT val FROM atable WHERE id=?");
selectIncrementedVal.setInt(1, id);
ResultSet resultSet = selectIncrementedVal.executeQuery();
if (resultSet.next()) {
    newVal = resultSet.getInt("val");
}
resultSet.close();
con.commit();
con.setAutoCommit(true);

请参阅下面有关交易隔离的信息。

http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html

http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_serializable

答案 1 :(得分:0)

  

我是否有权假设没有其他交易可以干扰   select ... for update之间的列val值,然后更新

是的,你可以。否则可能导致数据库中的“不一致”。

答案 2 :(得分:0)

我打算添加一些关于数据库当前隔离级别的内容,但是在我的不确定性中,我搜索了一些其他有趣的文章:   SQLServer Question和   Oracle-centric answer

最终,我不确定(对不起)。我可能会建议一个存储过程来提供您想要的显式原子锁,并在需要时让它将新值返回给您的代码。但是,存储过程语法可能是数据库供应商特定的。

(顺便说一句,你没有展示你如何获得连接参考。如果有多个线程同时使用相同的连接,他们将能够修改你背后的“自动提交”属性。您正在使用连接池,因此在任何给定时间只有一个线程具有此引用。)

另请注意,数据库隔离级别(如果证明是相关的)也是在连接级别设置的,因此如果您从池中获取连接,则前一个用户可能已将连接保留在不同于你所需要的状态。您谨慎恢复setAutoCommit()属性;您可能需要对setTransactionIsolation()属性执行相同操作。

答案 3 :(得分:0)

使用Oracle,您可以执行以下操作:

CallableStatement cs = con.prepareCall(
  "UPDATE atable SET val = val + 1 WHERE id=? RETURNING val INTO ?");
cs.setInt(1, id); 
cs.registerOutParameter(2, Types.NUMERIC); 
cs.execute();
int newId = cs.getInt(2);