oracle update 不提交事务,select for update不交由spring事务管理的正确姿势

明确一下概念

set autocommit = 0

关闭当前会话中事务的自动提交,需要手动 commit 或者 rollback,相当于开启一个全局的事务。在 mysql 的

事务中,默认autocommit = 1,每一次 sql 操作都被认为是一个单次的事务,被隐式提交。而oracle默认是

autocommit=0

start transaction

挂起 autocommit 的状态,开启一个事务上下文。首先数据库会隐式提交之前的还未被提交的操作,同时开启一个新事务。挂起autocommit 的意思是保存 autocommit 的当前状态,然后 start transaction,直到 commit or

rollback 结束本次事务,再恢复之前挂起的 autocommit 的状态。

如果 start transaction 前 autocommit = 1,则完成本次事务后 autocommit 还是 1

如果 start transaction 前 autocommit = 0,则完成本次事务后 autocommit 还是0,接下来的操作你仍需手动

commit 才可以提交。

SqlSession sqlSession = null;

try {

InputStream in = Resources.getResourceAsStream("mybatis-config.xml");

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);

// 不开启自动提交事务

sqlSession = sqlSessionFactory.openSession(false);

List 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中的方法

bVbEGz2

这里的queryFromDatabase就是查询数据库了

bVbEGAo

debug里面的doQuery,感觉距离真相越来越近了

bVbEGAx

这里的prepareStatement是关键

bVbEGBy

getConnection获取连接就能进行数据库操作了

bVbEGBG

这里的Connection中的setAudoCommit才是决定是否自动提交事务的关键,而sqlSessionFactory.openSession(false)设置autoCommit最终没有正确赋值给Connection才会导致事务没有正确提交,而我们使用Druid,对应的DruidPooledConnection的autoCommit默认是true,即自动提交事务

bVbEGCu

既然自动提交事务,那么sqlSession.commit(true)就无法强制提交事务了

bVbEGGo

最终是调用transaction.commit

bVbEGGU

因为它是自动commit,所以就没有执行connection.commit(),最终导致在查询结果为空没有执行下面的update语句时,selectForUpdate会没有提交事务锁住相应行。尽管查询结果为空,但它仍可以锁住行,比如这个例子中的sql是select * from user where age=18,尽管查询结果为空,但对于age=18的行锁仍然存在,当其它的sql插入age=18的数据时会被阻塞

bVbEGGX

解决办法有三种:

用sqlSession.getConnection().setAutoCommit(false);来设置autoCommit属性为false

提交时用直接调用connection的commit方法:sqlSession.getConnection().commit();

使用spring来开启事务

需要注意的是,上面的例子,在oracle数据库会阻塞,但mysql不会。原因就是mysql数据库是autocommit=1,会隐式commit,所以不会阻塞,而oracle是autocommit=0,但commit没有执行就阻塞了。

bVbEGJm

最后给出4个思考题梳理一下:

开启事务,事务不提交,for update悲观锁不会释放。(start transaction,必须要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不会阻塞。(druid的属性autocommit默认是true,mybatis认为我们已经提交事务了,不会执行commit,mysql的autocommit默认1,没影响,但oracle的autocommit默认是0,需要显式commit,所以阻塞了)

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,不会发生阻塞(由于druid的autocommit是false,所以mybatis认为事务没有提交,帮我们commit了,所以不会阻塞)

加spring事务并发for update操作,不会发生阻塞(由spring负责start transaction,执行sql,然后commit,完美,不会阻塞)

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();

}

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值