明确一下概念java
- set autocommit = 0
关闭当前会话中事务的自动提交,须要手动 commit 或者 rollback,至关于开启一个全局的事务。在 mysql 的
事务中,默认autocommit = 1,每一次 sql 操做都被认为是一个单次的事务,被隐式提交。而oracle默认是
autocommit=0mysql
- start transaction
挂起 autocommit 的状态,开启一个事务上下文。首先数据库会隐式提交以前的还未被提交的操做,同时开启一个新事务。挂起autocommit 的意思是保存 autocommit 的当前状态,而后 start transaction,直到 commit or
rollback 结束本次事务,再恢复以前挂起的 autocommit 的状态。
若是 start transaction 前 autocommit = 1,则完成本次事务后 autocommit 仍是 1
若是 start transaction 前 autocommit = 0,则完成本次事务后 autocommit 仍是0,接下来的操做你仍需手动
commit 才能够提交。spring
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不起做用(注意程序中的autoCommit的值与mysql中的autocommit没有任何关系,它只是一个属性,用来辅助代码最后的是否进行commit,它不是设置mysql的autocommit的值)。debug源码,跟踪executor.query中的方法 sql
这里的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的数据时会被阻塞数据库
解决办法有三种:mybatis
须要注意的是,上面的例子,在oracle数据库会阻塞,但mysql不会。缘由就是mysql数据库是autocommit=1,会隐式commit,因此不会阻塞,而oracle是autocommit=0,但commit没有执行就阻塞了。 并发
最后给出4个思考题梳理一下:oracle
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(); }
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(); } }
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(); } }