
两个线程同时查一行记录,然后更新记录。
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 呢?
1 Chyroc 2018 年 10 月 26 日 select for update |
2 wqlin 2018 年 10 月 26 日 RP 保证一个事务读不到另一个事务已经提交的修改。 可以试下 Read Commit,可以读到另一个事务已经提交的修改(没有试过) 另外,楼主的代码应该是: ``` synchronized (MyRunner.class) { .... } ``` 如果用 this,其实两个线程并没有互斥执行 |
3 imwxxxcxx OP @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 )。 |
4 lu5je0 2018 年 10 月 27 日 在我电脑上试了下,T1 和 T2 更新后都是 377 啊 |
7 wqlin 2018 年 10 月 27 日 @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 ``` |
8 wqlin 2018 年 10 月 27 日 @wqlin #7 补一下,mysql 不能处理 lost update 的问题,所以一个办法是使用 #1 提到的 for update 显式的锁住行; PostgreSQL,Oracle 这些可以检查到 Lost update。楼主可以参考下 Designing Data Intensive Application 的 第七章 transaction |
9 dbolo123 2018 年 10 月 27 日 via Android 最简单的应该就是把要 update 字段作为 where 条件,然后看 affect_row |