原本預期1小時的發布,為何最終發布加校驗實際花費時間5小時


這是一個簡單的數據生產導入的故事,原本故事情節應該是這樣的:數據整理-->測試驗證-->生產發布-->生產驗證,然后就是各回各家,所以這本來應該是一個平淡的故事,然而實際卻變成了如下情節:數據整理-->測試驗證-->生產發布-->生產驗證-->校驗失敗(預期數據未導入)-->問題排查-->解決問題-->生產發布-->生產驗證-->校驗問題(大部分數據是正確的,少部分數據不正確)-->問題排查(當時未能排查出原因,但能判斷出異常與生產原有的幾條異常數據有關)-->異常數據刪除sql編寫-->測試校驗-->生產發布-->生產校驗-->重新導入刪除部分數據(異常數據這次直接排除,沒包括在導入范圍)-->部分異常數據請示領導修正-->修正Sql准備-->測試校驗-->生產發布-->修正數據對應數據導入-->生產校驗!

你以為到這里就結束了?NO NO NO,故事怎么可能就這么結束,因為這批數據導入有對應的其它業務,還需要執行該部分業務,最終確認后才能各回各家,結果發現,坑爹的數據庫數據是修正了,但因為程序采用了Redis,異常數據還在Redis中,所以還要在Redis中刪除該部分異常數據,還好程序部分對此有處理,直接刪除沒導致程序功能異常,至此本次發布才算結束,但此時也已經是凌晨0點了,這真是一個悲劇的故事……

首先需要介紹下本次導入的豬腳,一個預先寫好,且已經發布至生產的存儲過程,另外該豬腳所在場景是MySql,其大致代碼精簡后如下

 1 DROP PROCEDURE IF EXISTS `usp_SadEvent`;
 2 DELIMITER $$
 3 CREATE PROCEDURE `usp_SadEvent`
 4 (
 5 IN identityNo VARCHAR(20),
 6 IN uName VARCHAR(15),
 7 IN cAmount LONG
 8 )
 9 label_at_start:
10 BEGIN
11 
12 SELECT @uid := id FROM `user`
13 WHERE identity_no=identityNo AND NAME=uName;
14 
15 IF @uid IS NULL THEN
16     select identityNo,uName,0 ret;
17   LEAVE label_at_start;
18 END IF;
19    update account set balance=balance+cAmount where uid=@uid;
20     select identityNo,uName,1 ret;
21 END label_at_start$$
22 DELIMITER ;

首先就是what the fuck的執行失敗問題,調用該存儲過程結果居然都是返回ret=0失敗!!

還好當初寫存儲過程時,考慮到了結果查詢,比較容易就發現為什么返回的uName是亂碼?碰上這種問題,直覺就是數據庫編碼有問題,一查果然如此

問題查出來了,怎么修正呢,正常劇情當然是改數據庫默認編碼為utf8了,但改編碼后Mysql必須要重啟,生產環境是你想重啟就能重啟的嗎?好吧,還好mysql存儲過程支持指定編碼,修改存儲過程,在uName部分指定編碼集是utf8(補充雖然數據庫默認字符集是latin1,但實際里面的表在創建時都默認指定utf8了,所以導致一直沒發現生產環境居然有默認編碼集問題)

IN uName VARCHAR(15) character set utf8,

改完后執行批量調用存儲過程的sql,大致如下

call usp_SadEvent('123131231313123132','張三',3000);#數據庫有
call usp_SadEvent('123454566778899999','李四',5000);#數據庫無李四,或數據庫里叫李斯

這里特別標明第一條數據庫里是有對應數據,第二條在數據庫中是查不到用戶數據的,執行結果居然發現第二條“李四”的金額,被加到了“張三”身上,這又是什么鬼!

其實問題就出在mysql的默認聲明參數上,只要在上面的調用語句下面再加一句

call usp_SadEvent('123131231313123132','張三',3000);#數據庫有
call usp_SadEvent('123454566778899999','李四',5000);#數據庫無
select @uid;

執行結果很明顯的就告訴你@uid是有值的,而且值為張三的uid,好吧,沒想到Mysql中

SELECT @uid := id 

這種隱式聲明參數的方式居然會在整個對話期間內都有效,所以還是老老實實改成如下顯式聲明才能測試正確

declare u_id long;
select `id` into u_id FROM `user`

最終完整修改后的存儲過程應該如下

DROP PROCEDURE IF EXISTS `usp_SadEvent`;
DELIMITER $$
CREATE PROCEDURE `usp_SadEvent`
(
IN identityNo VARCHAR(20),
IN uName VARCHAR(15) character set utf8,
IN cAmount LONG
)
label_at_start:
BEGIN
declare u_id long;
select `id` into u_id FROM `user`
WHERE identity_no=identityNo AND NAME=uName;

IF u_id IS NULL THEN
    select identityNo,uName,0 ret;
  LEAVE label_at_start;
END IF;
   update account set balance=balance+cAmount where uid=u_id;
    select identityNo,uName,1 ret;
END label_at_start$$
DELIMITER ;

哎,事后描述問題似乎很簡單,實際排查這些問題還是挺坑的,哎……


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM