明確一下概念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(); } }