欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

MySQL線上死鎖分析實戰(zhàn)

 更新時間:2021年02月25日 15:28:21   作者:是小桔啦  
這篇文章主要介紹了MySQL線上死鎖分析實戰(zhàn),文章內(nèi)容分析的很清楚,有對于這方面不懂的同學(xué)可以研究下

前言

MySQL 的鎖機制相信大家在學(xué)習(xí) MySQL 的時候都有簡單的了解過,那既然有鎖就必定繞不開死鎖這個問題。其實 MySQL 在大部分場景下是不會存在死鎖問題的(比如并發(fā)量不高,SQL 寫得不至于太拉胯的情況),但是在高并發(fā)的業(yè)務(wù)場景下,一不注意就會產(chǎn)生死鎖,而這個死鎖分析起來也比較麻煩。

前段時間在公司實習(xí)的時候就遇到了一個比較奇怪的死鎖,之前一直沒來得及好好整理,最近有空復(fù)現(xiàn)了一下,算是積累一點經(jīng)驗。

業(yè)務(wù)場景

簡單說一下業(yè)務(wù)背景,公司做的是電商直播,我負責(zé)的是主播端相關(guān)的業(yè)務(wù)。而這個死鎖就出現(xiàn)在主播后臺對商品信息進行更新的時候。

我們的一個商品會有兩個關(guān)聯(lián)的 ID,通過其中任何一個 ID 都無法確定唯一一件商品(也就是說這個 ID 和商品是一對多的關(guān)系),只能同時查詢兩個 ID,才能確定一件商品。所以在更新商品信息的時候,需要在 where 條件中同時指定兩個 ID,下面是死鎖 SQL 的結(jié)構(gòu)(已脫敏):

UPDATE test_table SET `name`="zhangsan" WHERE class_id = 10 AND teacher_id = 8;

這個 SQL 非常簡單,根據(jù)兩個等值條件,對一個字段進行更新。

不知道你看到這個 SQL 會不會懵逼,按常理來說,應(yīng)該是一個事務(wù)里有多條 SQL 才會有可能出現(xiàn)死鎖,這一條 SQL 怎么可能出現(xiàn)死鎖呢?

是的,我當(dāng)時也有這樣的疑惑,甚至懷疑是不是報警系統(tǒng)瞎報(最后證明不是…),當(dāng)時是真的摸不著頭腦。并且因為數(shù)據(jù)庫權(quán)限的原因,想看死鎖日志都看不到,又是臨近下班的時候,找 DBA 能麻煩死,所以就直接搜索引擎走起了……(關(guān)鍵詞:update 死鎖 單條 sql),最后查出來是由于 MySQL 的索引合并優(yōu)化導(dǎo)致的,即 Index Merge,下面會進行詳細講解并復(fù)現(xiàn)一下死鎖場景。

索引合并

Index Merge 是 MySQL 在 5.0 的時候引入的一項優(yōu)化功能,主要是用于優(yōu)化一條 SQL 使用多個索引的情況。

我們來看剛剛的 SQL,假設(shè) class_idteacher_id 分別是兩個普通索引:

UPDATE test_table SET `name`="zhangsan" WHERE class_id = 10 AND teacher_id = 8;

如果沒有 Index Merge 優(yōu)化的時候,MySQL 查詢數(shù)據(jù)的步驟如下:

  • 根據(jù) class_id 或 teacher_id (具體使用哪個索引由優(yōu)化器根據(jù)實際數(shù)據(jù)情況自行判斷,這里假設(shè)使用 class_id的索引)在二級索引上查詢到對應(yīng)數(shù)據(jù)的主鍵 ID
  • 根據(jù)查詢到的主鍵 ID 進行回標(biāo)查詢(即查詢聚簇索引),得到相應(yīng)的數(shù)據(jù)行
  • 從數(shù)據(jù)行中獲取 teacher_id ,判斷其是否等于 8,滿足條件則返回

從這個過程中,不難看出,MySQL 只使用到了一個索引,至于為什么不使用多個索引,簡單來說就是因為多個索引在多棵樹上,強行使用反而降低性能。

再來看看引入了 Index Merge 優(yōu)化后,MySQL 查詢數(shù)據(jù)的步驟如下:

  • 根據(jù) class_id 查詢到相應(yīng)的主鍵,再根據(jù)主鍵回表查詢到對應(yīng)的數(shù)據(jù)行(記為結(jié)果集 A)
  • 根據(jù) teacher_id 查詢到相應(yīng)的主鍵,再根據(jù)主鍵回表查詢到對應(yīng)的數(shù)據(jù)行(記為結(jié)果集 B)
  • 將結(jié)果集 A 和結(jié)果集 B 執(zhí)行交集操作,獲得最終滿足條件的結(jié)果集

這里可以看出,有了 Index Merge 之后,MySQL 將一條 SQL 語句拆分成了兩個查詢步驟,分別使用兩個索引,再用交集操作優(yōu)化性能。

死鎖分析

分析完了 Index Merge 的步驟,我們再回過頭想一下為什么會出現(xiàn)死鎖呢?

還記得上面說的 Index Merge 將一條 SQL 查詢拆分成了兩個步驟嗎,問題就出現(xiàn)在這里。我們知道 UPDATE 語句是會加上一個行級排他鎖的,在分析加鎖步驟之前,我們假設(shè)有如下一個數(shù)據(jù)表:

上表數(shù)據(jù)滿足我們文章開頭說的特點,根據(jù) class_idteacher_id 單個字段均無法唯一確定一條數(shù)據(jù),只能聯(lián)合兩個字段,才能確定一條數(shù)據(jù),并且設(shè)定 class_idteacher_id 分別為兩個普通索引。

假設(shè)有如下兩條 SQL 語句并發(fā)執(zhí)行,它們的參數(shù)完全不同,直覺告訴我們應(yīng)該不會出現(xiàn)死鎖,但直覺往往是錯誤的:

// 線程 A 執(zhí)行
UPDATE test_table SET `name`="zhangsan" WHERE class_id = 2 AND teacher_id = 1;

// 線程 B 執(zhí)行
UPDATE test_table SET `name`="zhangsan" WHERE class_id = 1 AND teacher_id = 2;

那么在 Index Merge 的優(yōu)化下,并發(fā)執(zhí)行如上 SQL 的時候,MySQL 的加鎖步驟如下:

最終,兩個事務(wù)互相等待,形成死鎖

解決方案

因為這個死鎖本質(zhì)上還是由于 Index Merge 這個優(yōu)化導(dǎo)致的,所以要解決這個場景的死鎖問題,本質(zhì)上只要讓 MySQL 不走 Index Merge 優(yōu)化即可。

方案一

手動將一條 SQL 拆分成多條 SQL,在邏輯層做交集操作,阻止 MySQL 的憨憨優(yōu)化行為,比如這里我們可以先根據(jù) class_id 查詢到相應(yīng)主鍵,再根據(jù) teacher_id 查詢相應(yīng)主鍵,最后根據(jù)交集后的主鍵查詢數(shù)據(jù)。

方案二

建立聯(lián)合索引,比如這里可以將 class_idteacher_id 建立一個聯(lián)合索引,MySQL 就不會走 Index Merge 了

方案三

強制走單個索引,在表名后添加 for index(class_id) 可以指定該語句僅走 class_id 索引

方案四

關(guān)閉 Index Merge 優(yōu)化:

  • 永久關(guān)閉:SET [GLOBAL|SESSION] optimizer_switch='index_merge=off';
  • 臨時關(guān)閉:UPDATE /*+ NO_INDEX_MERGE(test_table) */ test_table SET name="zhangsan" WHERE class_id = 10 AND teacher_id = 8;

場景復(fù)現(xiàn)

數(shù)據(jù)準(zhǔn)備

為了方便測試,這里提供一個 SQL 腳本,將其用 Navicat 導(dǎo)入后即可得到需要的測試數(shù)據(jù):

下載地址:https://cdn.juzibiji.top/file/index_merge_student.sql

導(dǎo)入之后,我們會得到如下格式的 10000 條測試數(shù)據(jù):

測試代碼

由于篇幅限制,這里僅給出代碼 Gist 鏈接:https://gist.github.com/juzi214032/17c0f7a51bd8d1c0ab39fa203f930c60

上述代碼主要是開啟 100 個線程執(zhí)行我們的數(shù)據(jù)修改 SQL 語句,來模擬線上并發(fā)情況,在運行幾秒鐘后,我們會得到下面這樣一個報錯:

com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction

這代表已經(jīng)產(chǎn)生了死鎖異常

死鎖分析

上面我們用代碼已經(jīng)構(gòu)造出了一個死鎖,接下來我們進入 MySQL 看看死鎖日志,在 MySQL 中執(zhí)行如下命令即可查看死鎖日志:

SHOW ENGINE INNODB STATUS;

在日志中,我們找到 LATEST DETECTED DEADLOCK 這一行,這里開始便是我們上次產(chǎn)生的死鎖,接下來我們開始分析。

通過第 29 行可以看到,事務(wù) 1 執(zhí)行的 SQL 的條件是 class_id = 6teacher_id = 16 ,它目前持有了一個行鎖,第 34~39 行是該行數(shù)據(jù),34 行是主鍵的十六進制表示,我們轉(zhuǎn)換為 10 進制即為 1616。同樣的,看 45 行,其等待拿鎖的是主鍵 id 1517 的數(shù)據(jù)。

接下來用同樣的方法分析事務(wù) 2,可知事務(wù) 2 持有了 3 把鎖,分別是主鍵 id 為1317、1417、1517 的數(shù)據(jù)行,等待的是 1616 。

看到這里我們就已經(jīng)發(fā)現(xiàn)了,事務(wù) 1 持有 1616 等待 1517,事務(wù) 2 持有1517 等待 1616,所以形成了一個死鎖。此時 MySQL 的處理方法是回滾持有鎖最少的事務(wù),并且 JDBC 會拋出我們前面的 MySQLTransactionRollbackException 回滾異常。

總結(jié)

這個死鎖在排查的時候其實非常不好排查,如果你不知道 MySQL 的 Index Merge,那么在排查的時候其實是毫無頭緒的,因為呈現(xiàn)在你面前的就只有一條非常簡單的 SQL,就算看死鎖日志,也是一樣的不明所以。

所以處理這類問題,更多的還是考驗?zāi)愕闹R儲備量和經(jīng)驗,只要遇到過一次,后面在寫 SQL 的時候多加注意就好了!

到此這篇關(guān)于MySQL線上死鎖分析實戰(zhàn)的文章就介紹到這了,更多相關(guān)MySQL線上死鎖分析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • CentOS 7下MySQL服務(wù)啟動失敗的快速解決方法

    CentOS 7下MySQL服務(wù)啟動失敗的快速解決方法

    CentOS 7下MySQL服務(wù)啟動失敗怎么辦?下面小編就為大家?guī)硪黄狢entOS 7下MySQL服務(wù)啟動失敗的快速解決方法?,F(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-03-03
  • 圖解Mysql中的LEFT?JOIN、RIGHT?JOIN與JOIN的區(qū)別

    圖解Mysql中的LEFT?JOIN、RIGHT?JOIN與JOIN的區(qū)別

    這篇文章主要介紹了圖解Mysql中的LEFT?JOIN、RIGHT?JOIN與JOIN的區(qū)別,Left?Join就是以左邊為基準(zhǔn),Inner?Join就是查兩個重復(fù)的部分,Right?Join就是以右邊為基準(zhǔn),需要的朋友可以參考下
    2023-11-11
  • MySQL自動填充create_time和update_time的兩種方式

    MySQL自動填充create_time和update_time的兩種方式

    當(dāng)我們創(chuàng)建業(yè)務(wù)表的時候 通常都需要設(shè)置create_time 和 update_time,下面這篇文章主要給大家介紹了關(guān)于MySQL自動填充createTime和updateTime的兩種方式,需要的朋友可以參考下
    2022-05-05
  • 允許任意IP訪問mysql數(shù)據(jù)庫的方法詳解

    允許任意IP訪問mysql數(shù)據(jù)庫的方法詳解

    MYSQL默認只能本地連接,即127.0.0.1和localhost,其他主機IP無法訪問數(shù)據(jù)庫,那么如何允許任意IP訪問mysql數(shù)據(jù)庫,所以本文小編將給大家介紹允許任意IP訪問mysql數(shù)據(jù)庫的方法,文中通過代碼示例介紹的非常詳細,需要的朋友可以參考下
    2024-01-01
  • MySQL中主鍵默認有索引嗎

    MySQL中主鍵默認有索引嗎

    MySQL主鍵默認是有索引的,在MySQL中,主鍵是用來唯一標(biāo)識表中每一行數(shù)據(jù)的字段或字段組合,主鍵的作用是保證數(shù)據(jù)的唯一性,并且可以提高數(shù)據(jù)的查詢效率,需要的朋友可以參考下
    2023-10-10
  • MySQL表添加某一列和刪除某一列的方法

    MySQL表添加某一列和刪除某一列的方法

    MySQL是一種廣泛使用的關(guān)系型數(shù)據(jù)庫管理系統(tǒng),它是一種開源的軟件,由瑞典的MySQL?AB公司開發(fā)的,這篇文章主要介紹了關(guān)于MySQL表添加某一列和刪除某一列的sql語句,需要的朋友可以參考下
    2023-06-06
  • 一文弄懂什么是MySQL的回表

    一文弄懂什么是MySQL的回表

    本文主要介紹了一文弄懂什么是MySQL的回表,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • 關(guān)于join?on和where執(zhí)行順序分析

    關(guān)于join?on和where執(zhí)行順序分析

    這篇文章主要介紹了join?on和where執(zhí)行順序,如果是inner?join,?放on和放where產(chǎn)生的結(jié)果一樣,?執(zhí)行計劃也是一樣,但推薦使用on,本文對join?on和where執(zhí)行順序給大家詳細講解,需要的朋友可以參考下
    2023-03-03
  • mysql出現(xiàn)“Incorrect key file for table”處理方法

    mysql出現(xiàn)“Incorrect key file for table”處理方法

    今天在恢復(fù)一個客戶數(shù)據(jù)的時候。發(fā)現(xiàn)了一貫問題。有多個表損壞了。因為都是直接把表復(fù)制進去的。然后就出現(xiàn)了這個問題問題
    2013-07-07
  • 學(xué)習(xí)mysql?如何行轉(zhuǎn)列與列傳行

    學(xué)習(xí)mysql?如何行轉(zhuǎn)列與列傳行

    這篇文章主要介紹了mysql行轉(zhuǎn)列與列傳行的使用方法,幫助大家更好的理解和學(xué)習(xí)MySQL的使用,語句不難,但有一定的知識參考價值,需要的朋友可以參考一下,希望給你的學(xué)習(xí)帶來幫助
    2022-02-02

最新評論