0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

MySQL并發(fā)Replace into導(dǎo)致死鎖場景簡析

數(shù)據(jù)庫和存儲(chǔ) ? 來源:MySQL內(nèi)核剖析 ? 2023-06-13 10:56 ? 次閱讀

在之前的文章 #issue 68021 MySQL unique check 問題中, 我們已經(jīng)介紹了在 MySQL 里面, 由于唯一鍵的檢查(unique check), 導(dǎo)致 MySQL 在 Read Commit 隔離級(jí)別也需要添加 GAP lock, 導(dǎo)致有些比較奇怪情況下有一些鎖等待.

另外一類問題是由于唯一鍵檢查導(dǎo)致的死鎖問題, 這類問題也非常多, 也是我們處理線上經(jīng)常收到用戶反饋的問題, 這里我們就分析幾個(gè)這樣死鎖的 Case.

Replace into 操作是非常常用的操作, 很多時(shí)候在插入數(shù)據(jù)的時(shí)候, 不確定表中是否已經(jīng)存在數(shù)據(jù), 有沒有唯一性的沖突, 所以會(huì)使用 replace into 或者 insert .. on duplicate update 這樣的操作, 如果沖突就把對應(yīng)的行給自動(dòng)更新.

但是這樣的操作在并發(fā)場景, 當(dāng)存在唯一鍵的時(shí)候容易有死鎖問題場景, 那么為什么會(huì)這樣, 我們來看一個(gè)簡單的 case:

通過GDB 和腳本可以復(fù)現(xiàn)以下死鎖場景.

create table t(a int AUTO_INCREMENT, b int, PRIMARY KEY (a), UNIQUE KEY (b));

insert into t(a, b) values (100, 8);

session1:
replace into t(a, b) values (10, 8);

session2:
replace into t(a, b) values (11, 8);
(40001): Deadlock found when trying to get lock; try restarting transaction

當(dāng)然也可以通過這個(gè)腳本, 不需要 GDB 就可以隨機(jī)復(fù)現(xiàn):

#! /bin/bash

MYSQL="mysql -h127.0.0.1 -P2255 -uroot test"

$MYSQL -e "create table t(a int AUTO_INCREMENT, b int, PRIMARY KEY (a), UNIQUE KEY (b))"

while true
do

  $MYSQL -e "replace into t(b) values (8)" &
  $MYSQL -e "replace into t(b) values (8)" &
  $MYSQL -e "replace into t(b) values (8)" &

  wait;
done

這里在并發(fā)session1 和 session2 插入的時(shí)候, 就容易出現(xiàn) Deadlock Lock 的問題, 類似用戶并發(fā)插入數(shù)據(jù)的場景.

dc8932ee-098e-11ee-962d-dac502259ad0.png

上面的死鎖信息 Trx HOLDS THE LOCK 和 WAITING FOR THIS LOCK TO BE GRANTED 是一個(gè)錯(cuò)誤的誤導(dǎo)信息, 官方版本在新的版本中已經(jīng)修復(fù), 這里 HOLDS THE LOCK 是不對的, 其實(shí)還未持有 X lock.

這里看到 Trx 1 waiting 在 8, 100 next-key X lock 上.

然后 Trx2 持有 8, 100 next-key X lock, 但是 WAITING FOR 8, 100 insert_intention lock.

那么為什么會(huì)有死鎖呢?

我們先看一下單個(gè) replace into 的流程

整體而言, 如果replace into 第1遍insert 操作的時(shí)候, 遇到unique index 沖牧, 那么需要重新執(zhí)行update 操作或者delete + 重新insert 操作, 但是第1遍insert 操作失敗添加的事務(wù)鎖并不會(huì)釋放, 而是等到整個(gè)事務(wù)提交才會(huì)釋放, 原因當(dāng)然是現(xiàn)在MySQL 2Phase Lock 機(jī)制要做的保證

replace into 大概代碼如下:

所有replace into/on duplicate key update 這里execute_inner 執(zhí)行的是Sql_cmd_insert_values => execute_inner() 方法

這里replace into/on duplicate key update 執(zhí)行在這個(gè)循環(huán)里面  if (duplicate_handling == DUP_REPLACE || duplicate_handling == DUP_UPDATE) {
    DBUG_ASSERT(duplicate_handling != DUP_UPDATE || update != NULL);    while ((error = table->file->ha_write_row(table->record[0]))) {        // ...
      if (duplicate_handling == DUP_UPDATE) {

    這里 branch 就是處理 on duplicate key update 的duplicate key 場景
    判斷如果是 on duplicate key update 邏輯, 那么遇到error 以后, 就是用 table->file->ha_update_row 通過 update 進(jìn)行更新
          } else /* DUP_REPLACE */ {
    duplicate_handling == DUP_REPLACE 就是處理 replace into 錯(cuò)誤場景
    在replace into場景中, 如果插入的key 遇到?jīng)_突的, 是如何處理的, 其實(shí)是分2種場景的:
    如果是 replace into 邏輯, 遇到 error 以后, 如果是沖突的是最后一個(gè) unique index, 并且沒有外鍵約束, 并且沒有delete trigger 的時(shí)候, 那么和 on duplicate key update 一樣, 使用 ha_update_row 通過 update 進(jìn)行更新

    否則通過 delete + 重新 insert 來進(jìn)行更新, 操作更多, 消耗也就更多.

    具體代碼:
    如果ha_write_row() 失敗, 那么會(huì)執(zhí)行delete_row() 操作, 等這個(gè)操作執(zhí)行完成以后, 又跳到這個(gè)while 循環(huán)進(jìn)行重新insert    if ((error = table->file->ha_delete_row(table->record[1]))) goto err;    /* Let us attempt do write_row() once more */
    }

接下來是2個(gè)replace into 操作的時(shí)候, 如果Thread 1 停在replace into 第一個(gè)階段, 也就是insert 遇到unique index 沖突, 此時(shí)持有8, 100 next-key lock.

這個(gè)時(shí)候第2個(gè)Thread 2也進(jìn)行replace into 操作, 在進(jìn)行唯一鍵沖突檢測, 執(zhí)行row_ins_scan_sec_index_for_duplicate() 的時(shí)候需要申請8, 100 next-key lock. 該lock 被thread 1持有, 那么只能進(jìn)行等待.

接下來Thread 1 繼續(xù)執(zhí)行, 執(zhí)行update 操作, 在InnoDB 里面, 對于二級(jí)索引而言需要執(zhí)行delete, 然后再insert 操作, 在insert 的時(shí)候需要持有8, 100 insert intention lock. 目前 InnoDB insert intention lock 判斷是否沖突的時(shí)候, 對應(yīng)的 record 不論是有事務(wù)等待或者已經(jīng)持有 next-key lock, 都算沖突. 此時(shí)Thread 已經(jīng)等在8, 100 next-key lock 上, 那么 Thread 1 就無法獲得 insert intention lock, 只能進(jìn)行等待.

這里有一個(gè)問題: 為什么申請insert_intention 的時(shí)候, 如果有其他事務(wù)提前等待在這個(gè) lock 的 next-key lock 上面, 那么這個(gè) insert_intention 會(huì)申請失敗?

dcb2378e-098e-11ee-962d-dac502259ad0.png

在函數(shù)rec_lock_check_conflict() 解釋了這個(gè)問題, 因?yàn)槿绻暾?intention lock 成功, 那么接下來的 insert 操作也就會(huì)成功, 那么原來等待這個(gè) record 上面的trx 就變成需要等待 2 個(gè) record 了.

比如如果之前 trx2 wait 在(4, 10] 這個(gè) next-key lock 上, 如果允許 trx1 插入了 7,這個(gè) record, 那么根據(jù)鎖繼承機(jī)制, 7 會(huì)繼承 10 這個(gè) record 上面的 next-key lock, 那么 trx2 就變成 wait 在兩個(gè) record 上, 也就變成 2 個(gè) waiting lock 了, 那么現(xiàn)有這套鎖等待喚醒機(jī)制就也要改了, 現(xiàn)在這套鎖等待喚醒機(jī)制因此一個(gè) trx 只會(huì)等待一個(gè) lock, 在一個(gè) lock 釋放以后, 相應(yīng)等待在這個(gè) Lock 上面的 trx 就可以喚醒了.

因此為了規(guī)避這樣的問題, MySQL InnoDB 里面如果申請 insert_intention lock 的時(shí)候, 如果有其他事務(wù)提前等待在這個(gè) lock 的 next-key lock 上, 那么 insert_intention lock 是無法申請成功的.

那么現(xiàn)在的就過就是 Thread 2 等待 Thread 1 next-key lock 釋放, Thread 1 等待 Thread 2 next-key lock 獲得并釋放, 出現(xiàn)了 Thread1 <=> Thread2 互相等待的情況 因此出現(xiàn)的死鎖.




審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • MYSQL數(shù)據(jù)庫
    +關(guān)注

    關(guān)注

    0

    文章

    96

    瀏覽量

    9390
  • GAP
    GAP
    +關(guān)注

    關(guān)注

    0

    文章

    15

    瀏覽量

    8307
  • Thread
    +關(guān)注

    關(guān)注

    2

    文章

    83

    瀏覽量

    25926
  • gdb調(diào)試器
    +關(guān)注

    關(guān)注

    0

    文章

    10

    瀏覽量

    1099

原文標(biāo)題:MySQL 常見死鎖場景 -- 并發(fā)Replace into導(dǎo)致死鎖

文章出處:【微信號(hào):inf_storage,微信公眾號(hào):數(shù)據(jù)庫和存儲(chǔ)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    STM32L5 boot_lock與rdp level配置導(dǎo)致死鎖如何解決?

    STM32L5 boot_lock 與 rdp level配置導(dǎo)致死鎖,應(yīng)該如何解決
    發(fā)表于 03-20 06:22

    HAL UART發(fā)送接收死鎖

    huart->State 盡然不是上面的2個(gè)中的一個(gè),然后返回HAL_BUSY狀態(tài),導(dǎo)致死鎖 請問哪位遇到過這種情況嗎?請指點(diǎn)下,系統(tǒng)使用了FREERTOS. 現(xiàn)象: 串口發(fā)送(沒用中斷發(fā)送),---->有接收,又觸發(fā)發(fā)送,導(dǎo)致死鎖
    發(fā)表于 01-02 10:05

    使用外部 SRAM 導(dǎo)致死機(jī)

    STM32單片機(jī)使用外部 SRAM 導(dǎo)致死機(jī)
    發(fā)表于 11-25 14:49 ?0次下載

    SPI接口發(fā)片選信號(hào)導(dǎo)致死機(jī)

    STM32F103 SPI接口發(fā)片選信號(hào)導(dǎo)致死機(jī)
    發(fā)表于 12-08 11:53 ?0次下載

    鼠標(biāo)HID例程(中)

    鼠標(biāo) HID 例程 緊接《鼠標(biāo) HID 例程(上)》一文,繼續(xù)向大家介紹鼠 標(biāo) HID 例程的未完的內(nèi)容。
    發(fā)表于 07-26 15:18 ?0次下載

    嵌入式系統(tǒng)死鎖檢測方法

    棘手。死鎖并發(fā)缺陷的典型問題,有時(shí)會(huì)導(dǎo)致整個(gè)嵌入式系統(tǒng)陷入癱瘓,嚴(yán)重影響嵌入式系統(tǒng)的穩(wěn)定性、可靠性。由于死鎖難以再現(xiàn)和修正,如何有效檢測死鎖
    發(fā)表于 01-31 10:27 ?0次下載
    嵌入式系統(tǒng)<b class='flag-5'>死鎖</b>檢測方法

    5G AAU 功放控制和監(jiān)測模塊

    5G AAU 功放控制和監(jiān)測模塊
    發(fā)表于 10-28 12:00 ?2次下載
    5G AAU 功放控制和監(jiān)測模塊<b class='flag-5'>簡</b><b class='flag-5'>析</b>

    APM32F103RCT6_UART_串口多層并發(fā)導(dǎo)致死機(jī)

    APM32F103RCT6_UART_串口多層并發(fā)導(dǎo)致死機(jī)
    發(fā)表于 11-09 21:03 ?4次下載
    APM32F103RCT6_UART_串口多層<b class='flag-5'>并發(fā)</b><b class='flag-5'>導(dǎo)致死</b>機(jī)

    MySQL并發(fā)update導(dǎo)致鎖等待介紹

    生產(chǎn)環(huán)境中經(jīng)常會(huì)遇到鎖等待與死鎖相關(guān)的問題,這類問題通常比較緊急,而且由于鎖相關(guān)影響因素較多,因此分析難度較大。
    的頭像 發(fā)表于 05-19 10:54 ?3889次閱讀
    <b class='flag-5'>MySQL</b><b class='flag-5'>并發(fā)</b>update<b class='flag-5'>導(dǎo)致</b>鎖等待介紹

    Linux內(nèi)核死鎖lockdep功能

    死鎖是指兩個(gè)或多個(gè)進(jìn)程因爭奪資源而造成的互相等待的現(xiàn)象,如進(jìn)程A需要資源X,進(jìn)程B需要資源Y,而雙方都掌握對方所需要的資源,且都不釋放,這會(huì)導(dǎo)致死鎖。 在內(nèi)核開發(fā)中,時(shí)常要考慮并發(fā)設(shè)計(jì),即使采用正確
    的頭像 發(fā)表于 09-27 15:13 ?723次閱讀
    Linux內(nèi)核<b class='flag-5'>死鎖</b>lockdep功能

    死鎖的現(xiàn)象及原理

    原理 1.1 復(fù)現(xiàn)最簡單的死鎖 線程A占有鎖1,線程B占有鎖2;此時(shí)線程A想要獲取鎖2,但是鎖2已經(jīng)被線程B占有, 此時(shí)線程A會(huì)休眠等待線程B釋放鎖2后,再去獲得鎖2??梢钥吹较旅娴?b class='flag-5'>場景,線程B想要獲取鎖1,結(jié)果線程B也休眠去了。這就導(dǎo)
    的頭像 發(fā)表于 11-10 16:32 ?473次閱讀
    <b class='flag-5'>死鎖</b>的現(xiàn)象及原理

    mysqlreplace的用法

    MySQL中,REPLACE是用于替換字符串或者更新特定記錄的關(guān)鍵字。它可以用于單個(gè)表或者多個(gè)表,允許你在已有的數(shù)據(jù)中查找指定的字符串并替換為新的字符串。REPLACE非常強(qiáng)大,可以根據(jù)你的需求
    的頭像 發(fā)表于 11-30 10:35 ?1824次閱讀

    MySQL替換字符串函數(shù)REPLACE

    MySQL是目前非常流行的開源數(shù)據(jù)庫管理系統(tǒng)之一,它具有強(qiáng)大的功能和性能。其中之一的字符串函數(shù)REPLACE,可以用于替換字符串中的指定字符或字符串。在本文中,我們將詳細(xì)討論MySQL替換字符串函數(shù)
    的頭像 發(fā)表于 11-30 10:44 ?1555次閱讀

    儲(chǔ)能的三大應(yīng)用場景

    儲(chǔ)能的三大應(yīng)用場景-古瑞瓦特 隨著太陽能風(fēng)能發(fā)電比例的不斷增長,可再生能源間歇性和不穩(wěn)定性的缺陷日益突出,不穩(wěn)定的光伏和風(fēng)電對電網(wǎng)的沖擊也日益嚴(yán)重。抽水儲(chǔ)能,壓縮空氣和蓄電池儲(chǔ)能等技術(shù)越來越被
    的頭像 發(fā)表于 12-20 16:30 ?1371次閱讀
    儲(chǔ)能的三大應(yīng)用<b class='flag-5'>場景</b><b class='flag-5'>簡</b><b class='flag-5'>析</b>

    淺談MySQL常見死鎖場景

    這里問題的原因是這個(gè) table 里面只有record 2, 所以這里認(rèn)真看, 死鎖的時(shí)候是等待在 supremum 上的, 因?yàn)閟upremum 的特殊性, supremum 沒有g(shù)ap lock, 只有 next-key lock
    的頭像 發(fā)表于 03-21 14:10 ?778次閱讀
    淺談<b class='flag-5'>MySQL</b>常見<b class='flag-5'>死鎖</b><b class='flag-5'>場景</b>