- SqlSession sqlSession = null;
- try {
- InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
- SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
- // 不开启自动提交事务
- sqlSession = sqlSessionFactory.openSession(false);
- List<User> list = sqlSession.selectList("com.shuang.test.findAllUsers");
- if (list.size()> 0) {
- sqlSession.update("xxx");
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- // 强制提交事务, 如果不设置的话, 只有在 insert 或 update 才会提交事务, 如果 selectForUpdate 结果为空, 无法进行 update 操作是无法提交事务的
- sqlSession.commit(true);
- sqlSession.close();
- }
上面的代码是在 oracle 数据库中进行的, 数据库连接池使用 druid, 代码看起来无任何问题. 而实际是当查询为空时, 不会执行下面的 update 语句, 而事务还是没有提交, 导致相应行被锁住了. 也就是 sqlSessionFactory.openSession(false) 中的发生 autoCommit 不起作用. debug 源码, 跟踪 executor.query 中的方法
这里的 queryFromDatabase 就是查询数据库了
debug 里面的 doQuery, 感觉距离真相越来越近了
这里的 prepareStatement 是关键
getConnection 获取连接就能进行数据库操作了
这里的 Connection 中的 setAudoCommit 才是决定是否自动提交事务的关键, 而 sqlSessionFactory.openSession(false) 设置 autoCommit 最终没有正确赋值给 Connection 才会导致事务没有正确提交, 而我们使用 Druid, 对应的 DruidPooledConnection 的 autoCommit 默认是 true, 即自动提交事务
既然自动提交事务, 那么 sqlSession.commit(true) 就无法强制提交事务了
最终是调用 transaction.commit
因为它是自动 commit, 所以就没必要执行 connection.commit(), 最终导致在查询结果为空没有执行下面的 update 语句时, selectForUpdate 会没有提交事务锁住相应行. 尽管查询结果为空, 但它仍可以锁住行, 比如这个例子中的 sql 是
select * from user where age=18
, 尽管查询结果为空, 但对于 age=18 的行锁仍然存在, 当其它的 sql 插入 age=18 的数据时会被阻塞
解决办法有三种:
用 sqlSession.getConnection().setAutoCommit(false); 来设置 autoCommit 属性为 false
提交时用直接调用 connection 的 commit 方法: sqlSession.getConnection().commit();
使用 spring 来开启事务
需要注意的是, 上面的例子, 在 oracle 数据库会阻塞, 但 MySQL 不会.
原因就要从 MySQL 和 oracle 对 sql 语句的运行机制说起, 平时我们说的开启事务是指在编码时开启事务, 但其实如果在编码时不开启事务, sql 语句也是在事务中执行的.
准确一点来说, 如果在编码时 start transaction(这里其实也就是开启事务并设置 audocommit 为 true) 或者设置 audocommit 为 true, 就是将事务交由 java 程序进行管理. 如果没有 start transaction 或 autocommit 为 false, 事务就交由数据库管理.
MySQL 会为每条执行 sql 语句自动提交事务, 无须显式提交 commit, 每条 sql 就是一个事务, 即在 autoCommit=true 时, selectForUpdate 也会提交事务, 而 oracle 不同于 MySQL,oracle 数据库底层需要显示提交事务. 再通俗一点来说, 设置 autoCommit=true 事务就是交由 java 程序管理, MySQL 此时对于每条 sql 语句都会进行 audocommit, 但 oracle 在事务交由 java 程序管理时, 只会对 insert 和 update 语句进行 commit, 而案例中的 selectForUpdate 将不会 commit, 最终导致数据库相应行阻塞
最后给出 4 个思考题梳理一下:
开启事务, 事务不提交, for update 悲观锁不会释放.(事务由 java 程序管理, 必须要 commit, 这个没什么好说的)
public void forupdateByTransaction() throws Exception { // 主线程获取独占锁 reentrantLock.lock(); new Thread(() -> transactionTemplate.execute(transactionStatus -> { // select * from forupdate where name = #{name} for update this.forupdateMapper.findByName("testforupdate"); System.out.println("==========for update=========="); countDownLatch.countDown(); // 阻塞不让提交事务 reentrantLock.lock(); return null; })).start(); countDownLatch.await(); System.out.println("==========for update has countdown=========="); this.forupdateMapper.updateByName("testforupdate"); System.out.println("==========update success=========="); reentrantLock.unlock(); }
不加 spring 事务并发执行 for update,oracle 会发生阻塞, 而 MySQL 不会阻塞.(虽然不加 spring 事务, 但 druid 的 autocommit 默认是 true, 即 connection 是自动提交事务, 事务交由 java 程序管理, MySQL 对每一条 sql 都会提交事务, 但对于 oracle 当事务交由 java 程序管理时, selectForUpdate 需要程序显式提交事务, selectForUpdate 语句不会根据 connection 的自动提交事务就能做到真正的提交事务)
public void forupdateByConcurrent() { AtomicInteger atomicInteger = new AtomicInteger(); for (int i = 0; i <100; i++) { new Thread(() -> { // select * from forupdate where name = #{name} for update this.forupdateMapper.findByName("testforupdate"); System.out.println("========ok:" + atomicInteger.getAndIncrement()); }).start(); } }
不加 spring 事务并发 for update 操作, 并且 druid 连接池 autocommit=false, 不会发生阻塞 (事务不交由 java 程序管理, 由数据库底层管理当然不会阻塞)
加 spring 事务并发 for update 操作, 不会发生阻塞 (事务的 commit 都交给 spring 管理了, 不会发生阻塞)
private void forupdateByConcurrentAndTransaction() { AtomicInteger atomicInteger = new AtomicInteger(); for (int i = 0; i <100; i++) { new Thread(() -> transactionTemplate.execute(transactionStatus -> { // select * from forupdate where name = #{name} for update this.forupdateMapper.findByName("testforupdate"); System.out.println("========ok:" + atomicInteger.getAndIncrement()); return null; })).start(); } }
来源: https://segmentfault.com/a/1190000022051705