在Mybatis中使用手動加鎖的方式操作資料庫
在使用Spring整合Mybatis進行資料庫操作時,我們可以通過Spring的註解@Transactional來實現事務,同時可以在註解中對資料庫設定隔離級別來進行併發操作資料庫時候的控制。
但是對於某些情況,僅僅使用資料庫隔離級別無法達到最優的效果,比如兩個事務同時對一張表進行操作,其中一個事務對錶進行讀取,而另一個事務對錶進行插入操作,在PostgreSQL,Orecal以及SQL Server中,由於採用的是讀已提交的隔離級別,所以當讀事務的鎖和寫事務的鎖是不衝突的,這就會導致這兩種事務可能會併發交替執行,最終的結果是如果讀事務在事務的生命週期中對錶進行了多次讀取的話,前後兩次可能讀取到不同的值,因為寫事務對資料庫的操作只要是經過Commit,就會對讀事務可見。將資料庫隔離級別設定為序列化可以解決這個問題,但是序列化會導致資料庫的併發能力降低。
我們都知道資料庫會提供一些加鎖的語句來人為對資料庫表進行加鎖,接下來就嘗試在Mybatis中動態對資料庫的表進行加鎖操作。
首先我們需要設定資料庫可以同時執行多條語句,即在配置檔案中的資料庫url的後面加上allowMultiQueries=true,這樣我們就可以在Mybatis的mapper.xml的標籤中寫入多行SQL語句來執行。
"jdbc:mysql://localhost:3306/xxx?allowMultiQueries=true"
接著我們隨意找一個Mybatis的xml檔案,在任意一句標籤中的SQL前面多加一句“LOCK TABLE users READ”。
<mapper namespace="hello.UserMapper">
<select id="getUser" parameterType="int" resultType="hello.User">
LOCK TABLE users READ;
select * from users where id=#{id};
</select>
</mapper>
然後在Console中開啟資料庫,找到users表,依次執行如下命令
START TRANSACTION;
LOCK TABLES users WRITE;
在這裡我們開啟了一個事務,在這個事務中首先是獲取users表的寫鎖,只要我們不執行commit或則rollback,這個事務將會一直持有這個鎖。
接著回到程式,在程式中我們執行如下程式碼
InputStream is = Main.class.getClassLoader().getResourceAsStream(resource);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession session = sessionFactory.openSession();
String statement = "hello.UserMapper.getUser";
User user = session.selectOne(statement, 1);
System.out.println(user);
觀察程式的輸出,此時我們可以看到控制檯沒有輸出,同時程式也沒有結束,而是處於阻塞狀態,此時再回到資料庫控制檯,輸入
COMMIT;
這時可以看到Mybatis程式的控制檯輸出了結果,因為當持有鎖的事務結束之後就會釋放鎖,這時嘗試獲取讀鎖的Mybatis程式就會獲取到被釋放的鎖,於是就可以往下繼續執行並最終回去查詢到的結果。
通過使用人工加鎖的方式可以很好地避免改變資料庫隔離級別來防止併發錯誤,同時大部分資料庫都提供了比表鎖更加精細的行鎖,可以大大提高併發的效率,這些鎖都是不能通過簡單使用Spring的註解來實現的,同時由於很多資料庫的設計思想是寫事務和讀事務是可以併發執行的,如果想要實現序列化的讀寫也可以嘗試使用人工加鎖的方式。