日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

生產上偶現這段代碼會出現死鎖,死鎖日志如下。

*** (1) TRANSACTION:
TRANSACTION 424487272, ACTIVE 0 sec fetching rows
MySQL tables in use 3, locked 3
LOCK WAIT 6 lock struct(s), heap size 1184, 4 row lock(s)
MySQL thread id 3205005, OS thread handle 0x7f39c21c8700, query id 567774892 10.14.34.30 finance Searching rows for update
update repay_plan_info_1
     SET actual_pay_period_amount = 38027,
        actual_pay_principal_amount = 36015,
        actual_pay_interest_amount = 1980,
        actual_pay_fee = 0,
        actual_pay_fine = 32,
        actual_discount_amount = 0,
        repay_status = 'PAYOFF',
        repay_type = 'OVERDUE',
        actual_repay_time = '2019-08-12 15:48:15.025'

     WHERE (  user_id = '938467411690006528'
                  and loan_order_no = 'LN201907120655461690006528458116'
                  and seq_no = 1
                  and repay_status <> 'PAYOFF' )

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 3680 page no 30 n bits 136 index `PRIMARY` of table `db_loan_core_2`.`repay_plan_info_1` trx id 424487272 lock_mode X locks rec but not gap waiting
Record lock, heap no 64 PHYSICAL RECORD: n_fields 33; compact format; info bits 0
 0: len 8; hex 800000000000051e; asc         ;;
 1: len 6; hex 0000193d35df; asc    =5 ;;
 2: len 7; hex 06000001d402e7; asc        ;;
 3: len 30; hex 323031393036313332303532303634323936303534323130353730303030; asc 201906132052064296054210570000; (total 32 bytes);
 4: len 30; hex 4c4e32303139303631333031323934303136393030303635323831373534; asc LN2019061301294016900065281754; (total 32 bytes);
 5: len 4; hex 80000002; asc     ;;
 6: len 18; hex 393338343637343131363930303036353238; asc 938467411690006528;;
 7: len 4; hex 80000003; asc     ;;
 8: len 4; hex 80000258; asc    X;;
 9: len 3; hex 646179; asc day;;
 10: SQL NULL;
 11: SQL NULL;
 12: len 8; hex 8000000000005106; asc       Q ;;
 13: len 8; hex 8000000000000000; asc         ;;
 14: len 8; hex 8000000000004e1e; asc       N ;;
 15: len 8; hex 8000000000000000; asc         ;;
 16: len 8; hex 80000000000002d6; asc         ;;
 17: len 8; hex 8000000000000000; asc         ;;
 18: len 8; hex 8000000000000000; asc         ;;
 19: len 8; hex 8000000000000000; asc         ;;
 20: len 8; hex 8000000000000012; asc         ;;
 21: len 8; hex 8000000000000000; asc         ;;
 22: len 8; hex 8000000000000000; asc         ;;
 23: len 8; hex 8000000000000000; asc         ;;
 24: len 8; hex 3230313930383131; asc 20190811;;
 25: len 7; hex 4f564552445545; asc OVERDUE;;
 26: SQL NULL;
 27: len 1; hex 59; asc Y;;
 28: SQL NULL;
 29: len 5; hex 99a35a1768; asc   Z h;;
 30: len 4; hex 5d503dd8; asc ]P= ;;
 31: SQL NULL;
 32: len 5; hex 99a3d80281; asc      ;;

*** (2) TRANSACTION:
TRANSACTION 424487271, ACTIVE 0 sec fetching rows
mysql tables in use 3, locked 3
5 lock struct(s), heap size 1184, 3 row lock(s)
MySQL thread id 3204980, OS thread handle 0x7f3db0cf6700, query id 567774893 10.14.34.30 finance Searching rows for update
update repay_plan_info_1
     SET actual_pay_period_amount = 20742,
        actual_pay_principal_amount = 19998,
        actual_pay_interest_amount = 726,
        actual_pay_fee = 0,
        actual_pay_fine = 18,
        actual_discount_amount = 0,
        repay_status = 'PAYOFF',
        repay_type = 'OVERDUE',
        actual_repay_time = '2019-08-12 15:48:15.025'


     WHERE (  user_id = '938467411690006528'
                  and loan_order_no = 'LN201906130129401690006528175485'
                  and seq_no = 2
                  and repay_status <> 'PAYOFF' )
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 3680 page no 30 n bits 136 index `PRIMARY` of table `db_loan_core_2`.`repay_plan_info_1` trx id 424487271 lock_mode X locks rec but not gap
Record lock, heap no 64 PHYSICAL RECORD: n_fields 33; compact format; info bits 0
 0: len 8; hex 800000000000051e; asc         ;;
 1: len 6; hex 0000193d35df; asc    =5 ;;
 2: len 7; hex 06000001d402e7; asc        ;;
 3: len 30; hex 323031393036313332303532303634323936303534323130353730303030; asc 201906132052064296054210570000; (total 32 bytes);
 4: len 30; hex 4c4e32303139303631333031323934303136393030303635323831373534; asc LN2019061301294016900065281754; (total 32 bytes);
 5: len 4; hex 80000002; asc     ;;
 6: len 18; hex 393338343637343131363930303036353238; asc 938467411690006528;;
 7: len 4; hex 80000003; asc     ;;
 8: len 4; hex 80000258; asc    X;;
 9: len 3; hex 646179; asc day;;
 10: SQL NULL;
 11: SQL NULL;
 12: len 8; hex 8000000000005106; asc       Q ;;
 13: len 8; hex 8000000000000000; asc         ;;
 14: len 8; hex 8000000000004e1e; asc       N ;;
 15: len 8; hex 8000000000000000; asc         ;;
 16: len 8; hex 80000000000002d6; asc         ;;
 17: len 8; hex 8000000000000000; asc         ;;
 18: len 8; hex 8000000000000000; asc         ;;
 19: len 8; hex 8000000000000000; asc         ;;
 20: len 8; hex 8000000000000012; asc         ;;
 21: len 8; hex 8000000000000000; asc         ;;
 22: len 8; hex 8000000000000000; asc         ;;
 23: len 8; hex 8000000000000000; asc         ;;
 24: len 8; hex 3230313930383131; asc 20190811;;
 25: len 7; hex 4f564552445545; asc OVERDUE;;
 26: SQL NULL;
 27: len 1; hex 59; asc Y;;
 28: SQL NULL;
 29: len 5; hex 99a35a1768; asc   Z h;;
 30: len 4; hex 5d503dd8; asc ]P= ;;
 31: SQL NULL;
 32: len 5; hex 99a3d80281; asc      ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 3680 page no 137 n bits 464 index `idx_user_id` of table `db_loan_core_2`.`repay_plan_info_1` trx id 424487271 lock_mode X locks rec but not gap waiting
Record lock, heap no 161 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 18; hex 393338343637343131363930303036353238; asc 938467411690006528;;
 1: len 8; hex 800000000000051e; asc         ;;

*** WE ROLL BACK TRANSACTION (2)

代碼定位

按照死鎖的update sql語句,我們先定位這個死鎖SQL中代碼是哪個代碼片段導致的。后面我們定位到,是如下代碼片段導致的:

MySQL死鎖分析:記一次因索引合并導致的MySQL死鎖分析過程

 


實際上一眼看上去,這段代碼有一個很典型的業務開發場景問題:開啟事務在for循環寫SQL。

注:這在實際的問題定位過程中并不容易,因為死鎖日志并不能反向直接定位到方法的對賬、線程名等,如果一個庫被多個服務同時連接,甚至定位是哪個服務都不容易。

死鎖分析(1)——猜測可能消息重發

  • 按照死鎖的必要條件:循環等待條件。即 T1事務應該持有了某把鎖L1,然后去申請鎖L2,而這時候發現T2事務已經持有了L2,而T2事務又去申請L1,這時候就發生循環等待而死鎖。
  • 一開始會猜測,是否我們更新表的順序在兩個事務里面是反方向的,即T1事務更新ta、tb表,鎖ta表的記錄,準備去拿tb表記錄的鎖;T2事務更新tb、ta表,鎖了tb記錄準備去拿ta的鎖,這是比較常見的死鎖情況。但是從SQL看,我們死鎖的SQL是同一張表的,即同一張表不同的記錄。
  • 而且從死鎖日志中可以發現,兩個死鎖的SQL居然是“一樣”的,也就是說是“同一條”SQL/同一段代碼(不同的where條件參數)導致的,。即上圖代碼中的這段for循環更新還款計劃的代碼。
  • 但是光這段For循環來看,如果要發生死鎖,有可能同一批請求,更新記錄的順序是反過來的,然后又并發執行的時候,可能出現。
  • 一開始會猜測上游觸發了兩條一樣的請求(我們這個場景是MQ重發),出現了并發,兩條消息分在兩個事務中并發執行。但是如果是MQ導致的原因,FOR循環更新的記錄順序是一樣的,一樣的順序意味著一樣的一樣的加鎖順序,一樣的加鎖順序意味著最多出現獲取鎖超時,不會滿足【循環等待】的條件,不可能死鎖。所以排除MQ重發的可能。

死鎖分析(2)

仔細閱讀出現問題的兩條SQL,可以發現一個規律,這里面都帶一個相同的where條件:userId= 938467411690006528,意味著這兩個事務的請求都來自一個用戶發起的,然后從actual_repay_time = '2019-08-12 15:48:15.025'來看,的確是瞬間一起執行的兩個事務,但是卻是不一樣的兩個借據。對應到真實的用戶的操作上,用戶的確有可能發起兩個借據的同時還款,例如同時結清多筆借據。

通過出現了幾次的死鎖,總結出了其相同的規律:每次的死鎖SQL條件都有一樣的特征——相同的userId+不同的借據+并發。基本可以斷定,相同的用戶在同時還款多筆的時候,可能會發現死鎖,但很可惜,測試環境、生產環境我們模擬這個場景都無法復現死鎖的情況。

只能靠技術手段分析原因了。

- 思路:這是了兩個完全不同的借據環境計劃,操作完全不一樣的數據記錄,為什么會發生死鎖呢?是不是鎖的不是行而是鎖了表?

死鎖日志分析

從事務1中的

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 3680 page no 30 n bits 136 index `PRIMARY` of table `db_loan_core_2`.`repay_plan_info_1` trx id 424487272 lock_mode X locks rec but not gap waiting

事務2中的

*** (2) HOLDS THE LOCK(S):

RECORD LOCKS space id 3680 page no 30 n bits 136 index `PRIMARY` of table `db_loan_core_2`.`repay_plan_info_1` trx id 424487271 lock_mode X locks rec but not gap

從RECORD LOCKS的標示可知,的確鎖的是行鎖不是表鎖。且從”but not gap”的信息來看,也不存在間隙鎖(注:我們線上隔離級別是read committed,本來就不存在間隙鎖問題)。所以鎖的位置應該的確是我們操作的行記錄才對。但是非常奇怪的是,實際業務上操作的記錄的確是完全隔離的(因為是不同的借據,記錄沒有交集),為什么會沖突呢?

再細節閱讀死鎖日志從事務2中獲取到了一點線索:

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 3680 page no 137 n bits 464 index `idx_user_id` of table `db_loan_core_2`.`repay_plan_info_1` trx id 424487271 lock_mode X locks rec but not gap waiting Record lock, heap no 161 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

這個索引很奇怪,是userid的索引?

分析之前,我們先看先看鎖持有情況:

  • T1等待鎖space id 3680 page no 30
  • T2持有鎖space id 3680 page no 30
  • T2等待鎖space id 3680 page no 137

最后回到了T2

可以推斷space id 3680 page no 137應該被T1持有了,但是日志中沒有顯示出來。

  • 3680 page no 30這個鎖是一個主鍵索引PRIMARY導致的,實際上我們沒有用到我們的自增主鍵,是非聚集索引,所以這是先鎖的非主鍵索引最后找到的主鍵去加鎖。
  • 3680 page no 137這個鎖就比較奇怪了,他鎖在了idx_user_id這個索引,這個索引是加在userId上的,也就是T2他正在嘗試鎖所有這個用戶的還款計劃的記錄!

如果是這樣,問題就解釋通了:

  • T1: 鎖了某行記錄X(具體怎么鎖的,從死鎖日志中未能獲取),然后準備去獲取LN201907120655461690006528458116,SEQ=1的記錄的鎖。
  • T2: 鎖了LN201907120655461690006528458116,SEQ=1的鎖,而他想去鎖所有userId=938467411690006528的記錄,這里面肯定包含了記錄X,所以他無法獲得X的鎖。
  • 這樣就造成死鎖了,因為X已經被T1持有了,而T1又在等T2釋放LN201907120655461690006528458116,SEQ=1這個鎖。

至于為什么T2明明準備操作
LN201906130129401690006528175485,SEQ=2的記錄,卻之前持有了
LN201907120655461690006528458116,SEQ=1的鎖,大概率不是因為之前的SQL真的操作
LN201907120655461690006528458116,SEQ=1的記錄,也是因為他之前本想持有別的記錄(從鎖的詳細信息上猜,可能是
LN2019061301294016900065281754的相關記錄),但是因為這個idx_user_id的索引問題,順帶鎖著了
LN201907120655461690006528458116,SEQ=1,因為都屬于一個userId。

所以從時間線上分析,順序應該是:

  • T1鎖了某記錄X
  • T2鎖了某記錄Y(從hold this lock的日志細節中推斷,是LN2019061301294016900065281754),然后準備鎖LN201906130129401690006528175485,SEQ=2,這時候的這條SQL觸發了idx_user_id,連帶一起鎖鎖住了LN201907120655461690006528458116,SEQ=1并準備鎖其它同用戶記錄
  • T1 執行下一條sql,準備獲取LN201907120655461690006528458116,SEQ=1的鎖,發現被T2獲取了,等待。
  • T2在鎖其它記錄的過程中發現了X,但是鎖不住,發現X被T1持有。而自己又持有了LN201907120655461690006528458116,SEQ=1這行記錄的鎖。
  • 這時候循環等待,死鎖!

所以根源是為什么SQL會使用idx_user_id這個索引呢?知道的可以在評論區和我討論

分享到:
標簽:死鎖 MySQL
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定