上週週三下午,準備去吃飯的時候,值班突然找過來說使用者操作時爆出訂單不存在的問題,因為之前做了分表連續很長一段時間都沒問題,而且當時找過來的都是一些因為產品或者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方法跑卻完全沒問題...