Mybatis二級緩存,你確定要用麼?

擊上方“程序猿技術大咖”,關注加羣討論

最近在優化項目,考慮到熱點操作進行緩存時,是否引入MyBatis的二級緩存並對此做了大量的調研、驗證,在此整合相關內容、分享給大家

一、Mybatis的緩存使用

大體就是首先根據你的sqlid,參數的信息自己算出一個key值,然後你查詢的時候,會先把這個key值去緩存中找看有沒有value,如果有,直接返回出來,就不查詢db了。如果沒有,那麼查詢db,然後將key,value保存到緩存中,以便下次使用。

1. 一級緩存

Mybatis對緩存提供支持,但是在沒有配置的默認情況下,它只開啓一級緩存,一級緩存只是相對於同一個SqlSession而言

所以在參數和SQL完全一樣的情況下,我們使用同一個SqlSession對象調用一個Mapper方法,往往只執行一次SQL,因爲使用SelSession第一次查詢後,MyBatis會將其放在緩存中,以後再查詢的時候,如果沒有聲明需要刷新,並且緩存沒有超時的情況下,SqlSession都會取出當前緩存的數據,而不會再次發送SQL到數據庫。

爲什麼要使用一級緩存,不用多說也知道個大概。但有以下幾個問題我們要注意一下。

一級緩存的生命週期有多長?

1)MyBatis在開啓一個數據庫會話時,會 創建一個新的SqlSession對象,SqlSession對象中會有一個新的Executor對象。Executor對象中持有一個新的PerpetualCache對象;當會話結束時,SqlSession對象及其內部的Executor對象還有PerpetualCache對象也一併釋放掉。

2)如果SqlSession調用了close()方法,會釋放掉一級緩存PerpetualCache對象,一級緩存將不可用。

3)如果SqlSession調用了clearCache(),會清空PerpetualCache對象中的數據,但是該對象仍可使用。

4)SqlSession中執行了任何一個update操作(update()、delete()、insert()) ,都會清空PerpetualCache對象的數據,但是該對象可以繼續使用。

怎麼判斷某兩次查詢是完全相同的查詢?

Mybatis認爲,對於兩次查詢,如果以下條件都完全一樣,那麼就認爲它們是完全相同的兩次查詢。

1)傳入的statementId。

2)查詢時要求的結果集中的結果範圍。

3)這次查詢所產生的最終要傳遞給JDBC java.sql.Preparedstatement的Sql語句字符串(boundSql.getSql() )。

4)傳遞給java.sql.Statement要設置的參數值。

2. 二級緩存

MyBatis的二級緩存是Application級別的緩存,它可以提高對數據庫查詢的效率,以提高應用的性能。

範圍是按照每個namepace緩存來存貯和維護,同一個namespace放到一個緩存對象中,當這個namaspace中執行了!insert、update和delete語句的時候,整個namespace中的緩存全部清除掉。

SqlSessionFactory層面上的二級緩存默認是不開啓的,二級緩存的開啓需要進行配置,實現二級緩存的時候,MyBatis要求返回的POJO必須是可序列化的。也就是要求實現Serializable接口,配置方法很簡單,只需要在映射XML文件配置就可以開啓緩存了<cache/>,如果我們配置了二級緩存就意味着:

  • 映射語句文件中的所有select語句將會被緩存。

  • 映射語句文件中的所欲insert、update和delete語句會刷新緩存。

  • 緩存會使用默認的Least Recently Used(LRU,最近最少使用的)算法來收回。

  • 根據時間表,比如No Flush Interval,(CNFI沒有刷新間隔),緩存不會以任何時間順序來刷新。

  • 緩存會存儲列表集合或對象(無論查詢方法返回什麼)的1024個引用

  • 緩存會被視爲是read/write(可讀/可寫)的緩存,意味着對象檢索不是共享的,而且可以安全的被調用者修改,不干擾其他調用者或線程所做的潛在修改。

二、Cache使用時的注意事項

1. 只能在【只有單表操作】的表上使用緩存

不只是要保證這個表在整個系統中只有單表操作,而且和該表有關的全部操作必須全部在一個namespace下。

2. 在可以保證查詢遠遠大於insert,update,delete操作的情況下使用緩存
這一點不需要多說,所有人都應該清楚。記住,這一點需要保證在1的前提下才可以!

三、避免使用二級緩存

可能會有很多人不理解這裏,二級緩存帶來的好處遠遠比不上他所隱藏的危害。

緩存是以namespace爲單位的,不同namespace下的操作互不影響。
insert,update,delete操作會清空所在namespace下的全部緩存。
通常使用MyBatis Generator生成的代碼中,都是各個表獨立的,每個表都有自己的namespace。

爲什麼避免使用二級緩存?
在符合【Cache使用時的注意事項】的要求時,並沒有什麼危害。
其他情況就會有很多危害了。

針對一個表的某些操作不在他獨立的namespace下進行。
例如在UserMapper.xml中有大多數針對user表的操作。但是在一個XXXMapper.xml中,還有針對user單表的操作。
這會導致user在兩個命名空間下的數據不一致。如果在UserMapper.xml中做了刷新緩存的操作,在XXXMapper.xml中緩存仍然有效,如果有針對user的單表查詢,使用緩存的結果可能會不正確。
更危險的情況是在XXXMapper.xml做了insert,update,delete操作時,會導致UserMapper.xml中的各種操作充滿未知和風險。
有關這樣單表的操作可能不常見。但是你也許想到了一種常見的情況。

多表操作一定不能使用緩存
爲什麼不能?
首先不管多表操作寫到那個namespace下,都會存在某個表不在這個namespace下的情況。
例如兩個表:role和user_role,如果我想查詢出某個用戶的全部角色role,就一定會涉及到多表的操作。

<select id="selectUserRoles" resultType="UserRoleVO">
    select * from user_role a,role b where a.roleid = b.roleid and a.userid = #{userid}
</select>

像上面這個查詢,你會寫到那個xml中呢??

不管是寫到RoleMapper.xml還是UserRoleMapper.xml,或者是一個獨立的XxxMapper.xml中。如果使用了二級緩存,都會導致上面這個查詢結果可能不正確。
如果你正好修改了這個用戶的角色,上面這個查詢使用緩存的時候結果就是錯的。
這點應該很容易理解。


在我看來,就以MyBatis目前的緩存方式來看是無解的。多表操作根本不能緩存。
如果你讓他們都使用同一個namespace(通過<cache-ref>)來避免髒數據,那就失去了緩存的意義。


四、挽救二級緩存

想更高效率的使用二級緩存是解決不了了。


但是解決多表操作避免髒數據還是有法解決的。解決思路就是通過攔截器判斷執行的sql涉及到那些表(可以用jsqlparser解析),然後把相關表的緩存自動清空。但是這種方式對緩存的使用效率是很低的。


設計這樣一個插件是相當複雜的,既然我沒想着去實現,就不廢話了。

最後還是建議,放棄二級緩存,在業務層使用可控制的緩存代替更好。

喜歡就點個"在看"唄,留言、轉發朋友圈

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章