我本來的想法是 READ_COMMITTED 只要commit()後的資料就應該要被讀取到,但實際上卻出現還是讀取到舊資料的問題。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 def testRepeatableRead () { Thread t1 = new Thread ({ testService.readData(2106 ) }) Thread t2 = new Thread ({ Thread.sleep(5000 ) modifyDataService.modifyData(2106 ) }) t1.start() t2.start() t1.join() t2.join() return "Success" }@Transactional(isolation=Isolation.READ_COMMITTED) def repeatReadData (Long id) { MeetingRoomBooking booking = MeetingRoomBooking.get(id) println "First read: ${booking?.content}" Thread.sleep(10000 ) booking = MeetingRoomBooking.get(id) println "Second read: ${booking?.content}" }@Transactional class ModifyDataService { def modifyData (Long id) { MeetingRoomBooking booking = MeetingRoomBooking.get(id) booking.content = "2" booking.save(flush: true ) println "Data updated to: ${booking.content}" } }
發生原因 出现这种情况的原因是 Grails 的一级缓存(Session 级别缓存)导致的。默认情况下,Grails 使用 Hibernate 作为 ORM 框架,并且 Hibernate 会维护一个一级缓存,这个缓存会存储当前 Session 中加载的所有对象。在你的 readData 方法中,第一次调用 MeetingRoomBooking.get(id) 时,会从数据库加载对象并缓存到当前 Session 中。在第二次调用 MeetingRoomBooking.get(id) 时,Hibernate 会直接从一级缓存中获取对象,而不会再去查询数据库
为了确保你在第二次读取时能读取到最新的数据,你可以清理一级缓存。你可以使用 withNewSession 方法或者 clear() 方法来达到这个目的。
解法
withNewSession
1 2 3 4 5 MeetingRoomBooking.withNewSession { booking = MeetingRoomBooking.get(id) println "Second read: ${booking?.content}"
clear()
1 2 3 4 def readData (Long id) { sessionFactory.currentSession.clear() }
Q: 那假設REPEATABLE_READ隔離層級下,可讀取到在其他地方被commit之後的資料嗎?
A: clear Session無效,只能建立新的Session。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Transactional(isolation = Isolation.READ_COMMITTED) def readData (Long id) { MeetingRoomBooking booking = MeetingRoomBooking.get(id) println "First read: ${booking?.content}" modifyData(id) Thread.sleep(5000 ) MeetingRoomBooking.withNewSession { booking = MeetingRoomBooking.get(id) println "Second Read:${booking?.content}" } booking = MeetingRoomBooking.get(id) println "Third read: ${booking?.content}" }
Q: 有辦法在交易裡面強迫commit()嗎?
A:可以,使用Hibernate Session
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 def sessionFactory@Transactional(isolation = Isolation.READ_COMMITTED) def readData (Long id) { MeetingRoomBooking booking booking = MeetingRoomBooking.get(id) println "First read: ${booking?.content}" Session session = sessionFactory.openSession() Transaction tx = null try { tx = session.beginTransaction() modifyData(id) Thread.sleep(5000 ) tx.commit() } catch (Exception e) { if (tx != null ) tx.rollback() throw e } finally { session.close() } MeetingRoomBooking.withNewSession { booking = MeetingRoomBooking.get(id) println "new Transaction Read:${booking?.content}" } booking = MeetingRoomBooking.get(id) println "Direct read: ${booking?.content}" }
Q: 那如果我提高隔離層級呢
A: 依然讀取的是Hibernate快取,不符合可重複讀特性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Transactional def readData (Long id) { MeetingRoomBooking booking = MeetingRoomBooking.get(id) println "First read: ${booking?.content}" Session session = sessionFactory.openSession() Transaction tx = null try { tx = session.beginTransaction() modifyData(id) Thread.sleep(5000 ) tx.commit() } catch (Exception e) { if (tx != null ) tx.rollback() throw e } finally { session.close() } MeetingRoomBooking.withNewSession { booking = MeetingRoomBooking.get(id) println "new Transaction Read:${booking?.content}" } booking = MeetingRoomBooking.get(id) println "Direct read: ${booking?.content}" }
Q: 確認是否因為同一個交易的Transaction導致Hibernate緩存改變
A: 拆開
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class MeetingTestService { @Transactional def readData (Long id) { MeetingRoomBooking booking = MeetingRoomBooking.get(id) println "First read: ${booking?.content}" Thread.sleep(10000 ) MeetingRoomBooking.withNewSession { booking = MeetingRoomBooking.get(id) println "new Transaction Read:${booking?.content}" } booking = MeetingRoomBooking.get(id) println "Direct read: ${booking?.content}" } }
嘗試降隔離層級
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class MeetingTestService { @Transactional(isolation = Isolation.READ_COMMITTED) def readData (Long id) { MeetingRoomBooking booking booking = MeetingRoomBooking.get(id) println "First read: ${booking?.content}" Thread.sleep(10000 ) MeetingRoomBooking.withNewSession { booking = MeetingRoomBooking.get(id) println "new Transaction Read:${booking?.content}" } booking = MeetingRoomBooking.get(id) println "Direct read: ${booking?.content}" } }
如果清除Hibernate快取呢
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 SessionFactory sessionFactory @Transactional(isolation = Isolation.READ_COMMITTED) def readData (Long id) { MeetingRoomBooking booking booking = MeetingRoomBooking.get(id) println "First read: ${booking?.content}" Thread.sleep(10000 ) MeetingRoomBooking.withNewSession { booking = MeetingRoomBooking.get(id) println "new Transaction Read:${booking?.content}" } sessionFactory.currentSession.clear() booking = MeetingRoomBooking.get(id) println "Direct read: ${booking?.content}" }
大概測試是這樣
結論
Hibernate 的一級緩存機制會影響資料讀取的行為
要讀取最新資料,可以:
使用 withNewSession
清除當前 Session 緩存
在使用 READ_COMMITTED 隔離層級時,需要特別注意緩存的影響
使用獨立 Session 進行修改可以確保資料立即提交