请教一个关于数据库事务的问题

2018-10-26 18:47:47 +08:00
 mayowwwww

两个线程同时查一行记录,然后更新记录。

class MyRunner implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + ":连接数据库...");
            Connection connection = null;
            try {
                connection = DriverManager.getConnection(RepeatableRead.DB_URL, RepeatableRead.USER, RepeatableRead.PASS);
                connection.setAutoCommit(false);
                connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
                String querySql = "select * from account where id=?";

                PreparedStatement queryStmt = connection.prepareStatement(querySql);
                queryStmt.setInt(1, 1);
                ResultSet set = queryStmt.executeQuery();
                set.next();
                int amount = set.getInt("amount");
                System.out.println(Thread.currentThread().getName() + ":Amount=" + amount);
                Thread.sleep(1000);
                // 读取了数据,未提交事务。
                synchronized (this) {
                    String updateSql = "update account set amount=? where id=1";
                    PreparedStatement updateStmt = connection.prepareStatement(updateSql);
                    updateStmt.setInt(1, amount + 100);
                    int count = updateStmt.executeUpdate();
                    if (count > 0) {
                        set = queryStmt.executeQuery();
                        set.next();
                        amount = set.getInt("amount");
                        System.out.println(Thread.currentThread().getName() + ":更新成功...|" + amount);
                        connection.commit();
                    }
                }
            } catch (SQLException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Test
    public void testMyRunner() throws ClassNotFoundException, SQLException, InterruptedException {
        MyRunner myRunner = new MyRunner();
        Thread t1 = new Thread(myRunner, "T1");
        Thread t2 = new Thread(myRunner, "T2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
    }

/* 
输出:
T1:连接数据库...
T2:连接数据库...
T2:Amount=277
T1:Amount=277
T2:更新成功...|377
T1:更新成功...|277

T1 为什么还是 277 ?
*/

由于隔离级别是 RR,T1 不应该也输出 377 吗?

另外要怎么才能保证最后 amount 得到正确的结果 447 呢?

2489 次点击
所在节点    Java
10 条回复
Chyroc
2018-10-26 19:21:04 +08:00
select for update
wqlin
2018-10-26 21:07:18 +08:00
RP 保证一个事务读不到另一个事务已经提交的修改。
可以试下 Read Commit,可以读到另一个事务已经提交的修改(没有试过)
另外,楼主的代码应该是:

```
synchronized (MyRunner.class) {
....
}
```
如果用 this,其实两个线程并没有互斥执行
mayowwwww
2018-10-26 23:37:32 +08:00
@wqlin
``` java
MyRunner myRunner = new MyRunner();
Thread t1 = new Thread(myRunner, "T1");
Thread t2 = new Thread(myRunner, "T2");
```
锁定的应该都是 myRunner 这个实例对象吧。
REPEATABLE READ 的确是读不到其他会话的修改,select 查询并没有问题。
问题是当前线程的 update 并没有生效(如果生效的话 T1 应该也是 377 )。
lu5je0
2018-10-27 00:27:37 +08:00
在我电脑上试了下,T1 和 T2 更新后都是 377 啊
mayowwwww
2018-10-27 00:49:21 +08:00
@lu5je0 mysql 么?其他数据库可能事务隔离级别不一样看到的结果也不一样。
lu5je0
2018-10-27 01:43:50 +08:00
@mayowwwww 是 mysql
wqlin
2018-10-27 10:42:21 +08:00
@mayowwwww #3
抱歉看错了,用 this 也可以。
不过为啥楼主你更新不成功,我试了一下,最后 T1 和 T2 都是 377。这应该算是 Lost Update 吧,一个 transaction 覆盖了另一个 transaction 更新的值。
如果要输出 447,我觉得可以用线程互斥+ Read Committed,一个线程先 commit,然后另一个线程再开启一个 transaction,代码见: https://gist.github.com/wqlin/e957e01fcb40986996b71831a4c45404
在我的电脑上输出为:
```
T1:连接数据库...
T2:连接数据库...
T2:Amount=400
T2:更新成功...|500
T1:Amount=500
T1:更新成功...|600
```
wqlin
2018-10-27 12:18:47 +08:00
@wqlin #7 补一下,mysql 不能处理 lost update 的问题,所以一个办法是使用 #1 提到的 for update 显式的锁住行; PostgreSQL,Oracle 这些可以检查到 Lost update。楼主可以参考下 Designing Data Intensive Application 的 第七章 transaction
dbolo123
2018-10-27 17:16:57 +08:00
最简单的应该就是把要 update 字段作为 where 条件,然后看 affect_row
dbolo123
2018-10-27 17:17:42 +08:00
@dbolo123 为 0 就重试

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/501510

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX