mysql會話變量和分區的坑
最近在新東家做審批系統,業務邏輯和工作流引擎都嚴重依賴mysql。其中業務邏輯部分大量用到存儲過程,幾乎所有核心dal都是用存儲過程實現的。使用存儲過程的優勢是對於復雜的數據插入和更新操作效率很高。如果不使用存儲過程,一個復雜的操作可能要請求多次數據庫才能完成,但使用存儲過程只需要請求一次,節省網絡請求開銷。但存儲過程的壞處也很明細:不方便測試和調試。我們前一陣就碰到一個存儲過程的bug,困擾了一陣。
某字段莫名其妙被篡改
有一天,同事發現數據庫裏面存的某個字段和原始申請單裏面的字段不一樣。而且奇怪的是,並非所有的申請單都是這樣,有的是對的,有的不對。我檢查了所有可能插入和修改該字段的代碼,發現總共只有兩個地方會寫入該字段,並且這兩個地方都是新增記錄,不會修改。按理說只要存儲過程參數傳遞正確,該字段就會正確地寫入,並且不再變化。但事實卻是,這兩個地方寫入的兩種記錄都會不確定性地出現字段被篡改的問題。
憑直覺,這種不確定性出現的問題,有可能是線程安全問題引起的。一開始,我們首先懷疑是參數在某個地方被非線程安全的訪問,導致被詭異修改。排查代碼,我們發現代碼邏輯沒有問題,不存在這種可能性。那只可能是存儲過程的問題,後來終於找到原因。
這個bug是這樣產生的。我們數據庫裏有一些碼表,用來定義一些業務相關的類型,比如商品類型表(goods_type),它的定義類似這種:
|id|code|description| |--|----|-----------| |-2| XER| code有誤 | |0 | A | 日用品 | |1 | B | 家電 | |2 | C | 廚房電器 |
用戶的申請單中會包含商品類型信息(商品類型code),申請單保存到數據庫的時候,會為每件商品存儲一條記錄,記錄中包含該商品在商品類型表中的id。
用於保存商品信息部分的存儲過程中有類似這樣的語句:
select id into @goods_id from good_type where code=@goods_code;
if @goods_id = NULL then
set @goods_id = -2;
end if
上面語句中@goods_id
,@goods_code
都是會話變量,會在同一個會話中共享。@goods_code
是從存儲過程參數中得到的商品code。
當@goods_code
是一個在goods_type中找不到的code時,上面的select
語句不會修改@goods_id
的值,因此@goods_id
@goods_id
,那該商品保存的商品類型id就會“莫名其妙“的被篡改。如果當前會話之前的語句沒設置過@goods_id
變量,那@goods_id
值為NULL,就會執行後面的if語句。由於我們使用數據庫連接池來訪問數據庫,連接會被復用,同一個會話就執行多條不同的語句,所以就會不確定性的出現字段被篡改的現象。
有兩種方式修復該bug,一是在select
語句前加上初始化語句set @goods_id = NULL;
,這樣每次使用前都保證會重新初始化變量;另一種方式是改成使用局部變量,不用會話變量。
mysql分區的坑
前一陣我們發現mysql寫入、刪除壓力大,於是決定對其中一個關鍵表分區,緩解壓力。該表有varchar(64)類型的主鍵,並且主鍵的生成策略類似UUID。我們使用mysql的key函數按主鍵分區,一開始設置分區數為64,發現所有的數據全分到一個區。剛開始懷疑是分區語句的問題,但經過確認,分區語句沒啥問題。後來調整分區數,發現分區數為奇數時分區比較均勻,偶數有問題。
‘c7e74234-40c7-11e6-a64b-7ce9d3efdb89‘,
‘c7e74232-40c7-11e6-a64b-7ce9d3efdb89‘,
‘c7e74230-40c7-11e6-a64b-7ce9d3efdb89‘,
‘c7e609a5-40c7-11e6-a64b-7ce9d3efdb89‘,
‘c7e609a3-40c7-11e6-a64b-7ce9d3efdb89‘
觀察生成的主鍵,發現主鍵中間和後面的部分都一樣,只有前面部分不同,猜測key函數分區實現和後面部分關系比較大。有空看看源碼分析一下具體原因。
mysql會話變量和分區的坑