Java.sql.Connection隔离级别

3

我正在编写一些测试代码,但结果与我的期望不符。

我有一个表,基本上是计数器的行。其他表使用这些行生成应该是唯一的ID。

当我运行下面的代码时,我期望的是第一个到达select语句的线程将锁定该行或表,停止对唯一ID值的所有读取或写入操作。然而,由于第二个线程被休眠了1秒钟,所以它总是在第一个线程之前完成,因此它们都读取相同的值并写入相同的值,所以它只增加了一次而不是两次,这是我期望的。

我的代码有问题吗?还是我对隔离级别的理解不正确?

我已经删除了样板代码。使用MySQL数据库的标准sql.Connection。

private void incrementValue() {

        connection
                .setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);

        statement = connection.createStatement();

        System.out.println(Thread.currentThread().getName()
                + " doing select");
        resultSet = statement.executeQuery("select * from counter");
        System.out.println(Thread.currentThread().getName()
                + "after select");
        if (counter++ == 0) {
            Thread.sleep(1000);
        }
        String incrementedValue = getIncrementedValue(resultSet);

        statement.executeUpdate("update counter set counter='"
                + incrementedValue + "'");


}

private String getIncrementedValue(ResultSet resultSet) throws SQLException {
    String value = "";
    if (resultSet.next()) {
        System.out.println(Thread.currentThread().getName() + "Value was "
                + resultSet.getString(1));

        value = (new Integer(resultSet.getString(1)) + 1) + "";

    }

    return value;

}

这是从主函数调用的

public static void main(String[] args) {
    DatabaseExample databaseExample = new DatabaseExample();

    Runnable runnable = new Runnable() {

        @Override
        public void run() {
            DatabaseExample databaseExample = new DatabaseExample();
            databaseExample.incrementValue();
        }
    };
    new Thread(runnable).start();

    databaseExample.incrementValue();
}

在这种情况下,锁必须在应用程序级别上进行。只需使用关键字synchronized同步您的方法即可。请注意,这将影响应用程序的性能。 - Luiggi Mendoza
你可能想要考虑使用序列或标识生成你的 ids,或者使用 Hilo 方案。任何一种方案都比你描述的好。 - Nathan Hughes
1个回答

3
即使在SERIALIZABLE隔离级别下,也可以并行进行多个选择。如果您想在select子句中对行进行锁定,请使用select ... for update
参考资料: http://dev.mysql.com/doc/refman/5.1/en/select.html

如果您在使用使用页面或行锁的存储引擎时使用FOR UPDATE,则查询检查的行将被写入锁定,直到当前事务结束。使用LOCK IN SHARE MODE设置允许其他事务读取已检查的行但不能更新或删除它们的共享锁。

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

SERIALIZABLE

此级别类似于REPEATABLE READ,但如果禁用自动提交,则InnoDB会隐式地将所有普通SELECT语句转换为SELECT ... LOCK IN SHARE MODE。


JB Nizet:感谢您的回复。我希望有一个非特定于数据库的解决方案,因为它需要在来自不同供应商的数据库上运行,但似乎没有这样的解决方案。在MySQL和Oracle上使用FOR UPDATE,在SQL Server上使用WITH(UPDLOCK)似乎可以工作,同时捕获死锁异常并重新运行代码。 - Medu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接