mybatis LocalCache踩坑記錄

2021-09-27 08:26:42 字數 3601 閱讀 9023

上週週三下午,準備去吃飯的時候,值班突然找過來說使用者操作時爆出訂單不存在的問題,因為之前做了分表連續很長一段時間都沒問題,而且當時找過來的都是一些因為產品或者qa操作不當找不到記錄的情況,就沒有在意這些,當時以為幾分鐘就能搞定,但是沒想到居然是線上日誌爆出的問題,經過驗證訂單確實不存在!心想完了,晚飯沒了,說不定地鐵尾班車也沒了

聊這次問題之前向先交代一下背景,之前做了分表,分表邏輯很簡單,靠著一筆訂單中上游系統生成的訂單的訂單號a作為路由 id 去區分這筆訂單究竟去入到哪張表裡,但是一筆訂單有很多訂單號,如果我想根據我們傳給下游系統的訂單號b去查詢記錄該怎麼辦呢?於是針對這種場景我們做了乙個對映表,專門維護a b間對映的表,詳情如下圖

插入的示意圖如下所示

以路由id查詢的方式如下圖所示,跟插入類似

以非路由id去查詢訂單的示意圖

其實我覺得蠻容易看懂的,無非多了一步對映

其實通過上述邏輯不難看出,對於非路由id非常依賴對映表,這次問題的產生也是因為對映表

當我們檢視這些單子的時候發現所有單子都是一種情況就是order表有資料,對映表沒資料,當時覺得很奇怪,因為其他業務先也用到了這塊入錶的邏輯但為什麼其他業務沒問題

先來介紹一我們對於對映表和訂單表的處理邏輯 

@componet

public class orderinfoproxy

}public void selectbyidc(string idc)

}

再來看下我們對於業務的處理邏輯,具體**設計場景較多不一一枚舉了,只用偽**表示處理邏輯

@service

public class orderserviceimpl}}

業務邏輯很簡單,就是根據idc(非路由id)去查詢表裡的資料,然後做一些後處理邏輯,如果達成某個條件,就把idb(非路由id)塞到order實體裡然後更新記錄,最後事務會保證兩張表的資料一致性,但是很奇怪的是當時表的資料反而不一致,orderproxy更新處理邏輯很簡單,根據路由ida查詢在order記錄,更新order表,若庫中記錄中idb不存在且更新的實體中存在idb則更新對映表,看起來也沒啥問題,看來只能模擬線上進行debug了

之後開啟debug,在updateorder的selectbyida之後打上斷點,此時發現select後idb已經存在了?!但是資料庫中此時沒有這條記錄的idb欄位,當時想了很久,突然想起mybatis的快取,從上面**仔細看不難發現我們呼叫了兩次selectbyida()並且在同乙個事務中,spring事務是會跟session繫結的,因為事務預設為required傳播級別所以是乙個事務,session也是同乙個,所以第二次查詢,也就是我們更新時候的查詢,根本就沒有去查資料庫!而是用的快取資料!

我們肉眼看的的idb也不是更新到資料庫中的idb,當然結果好像是我們對映表和order表不一致因為事務問題,但根本上在未更新的時間段裡他們的資料是一致的,不管是對映表還是order表都沒有idb,那麼查出來的記錄為什麼有idb呢,那是因為在這段**中滿足了某個條件而設的idb

if(達成某個條件)
那麼為什麼我select返回的是我請求的時的order實體呢,我們來看下原始碼

@override

errorcontext.instance().resource(ms.getresource()).activity("executing a query").object(ms.getid());

if (closed)

//判斷是否沒有需要查詢的資料了或者需要重新整理快取

if (querystack == 0 && ms.isflushcacherequired())

listlist;

try else

} finally

if (querystack == 0)

// issue #601

deferredloads.clear();

//快取作用域如果是statement則重新整理一級快取

if (configuration.getlocalcachescope() == localcachescope.statement)

}return list;

其實看下邏輯還是挺簡單的,根據key獲取快取資料,如果獲取不到則走db,不然直接拿快取資料,那麼兩次資料為什麼一致呢,其實我們還要看下key的生成邏輯

@override

if (closed)

cachekey cachekey = new cachekey();

cachekey.update(ms.getid());

//預設都使用defaultrowbounds

cachekey.update(rowbounds.getoffset());

cachekey.update(rowbounds.getlimit());

//sql也是走的同乙個

cachekey.update(boundsql.getsql());

//對映跟引數也是相同的

typehandlerregistry typehandlerregistry = ms.getconfiguration().gettypehandlerregistry();

// mimic defaultparameterhandler logic

object value;

if (boundsql.hasadditionalparameter(propertyname)) else if (parameterobject == null) else if (typehandlerregistry.hastypehandler(parameterobject.getclass())) else

cachekey.update(value);}}

if (configuration.getenvironment() != null)

return cachekey;

其實根本也不用仔細看,只要注意下有沒有類似於時間戳之類的會導致不是同乙個key的因素就可以,其實是沒有的,那麼基本都可以推斷出是mybatis的一級快取惹的禍

那麼怎樣讓mybatis不使用一級快取呢,讓我們來看下**,可以看出有三種方式

1、不使用事務,因為不使用事務就不會用同乙個session就不會導致使用快取,但明顯這種方案是削足適履

2、使用flushcashe=true,看到原始碼中在查詢之前會判斷是否需要快取,不需要就會重新整理

3、設定statementtype為statement級別,在原始碼最後的判斷邏輯裡會判斷是否是statement,如果是,則重新整理快取

方式一

//***

方式二

//***x

AdMob接入踩坑記

首先列出參考文件 admob官方參考鏈結 我是cocos2d x v3.9的工程,在按照官方文件接入之後,出現一堆編譯錯誤例如 plain view plain copy undefined symbols for architecture arm64 objc class glkview refe...

python codecs 模組踩坑記

之前在使用 codecs 模組進行檔案讀寫的時候,常用習慣 如下 import codecs 讀取data codecs.open file name r utf 8 read 寫入fw codecs.open file name w utf 8 fw.write data 之前這麼寫好像也沒什麼問...

Android Java RSA加密踩坑記

最近公司在做乙個新產品,後台是另一批人,然後通訊加密方法也換了 沒辦法,在他們那copy了加解密的utils然後一通聯調,要死。cipher cipher cipher.getinstance keyfactory.getalgorithm 就拋異常了,但是在utils裡寫main方法跑卻完全沒問題...