最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

MySQL?INSERT?導(dǎo)致的死鎖問(wèn)題分析及解決方案

 更新時(shí)間:2025年08月28日 08:58:59   作者:FatalFlower  
本文分析MySQL8.4.6中并發(fā)INSERT導(dǎo)致的死鎖,指出因間隙鎖(由外鍵約束和主鍵UUID引發(fā))及頁(yè)分裂操作形成循環(huán)等待,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧

前言

本文選用的 MySQL 版本:8.4.6

使用的數(shù)據(jù)

表結(jié)構(gòu):

DROP TABLE IF EXISTS store_snapshot_ext;
DROP TABLE IF EXISTS store_snapshot;
create table store_snapshot
(
    id           varchar(32) not null comment '主鍵'
        primary key,
    warehouse_id varchar(32) null comment '倉(cāng)庫(kù)主鍵',
    snap_date    datetime    null comment '快照日期',
    create_id    varchar(32) null comment '創(chuàng)建人id',
    create_time  datetime    null comment '創(chuàng)建日期',
    modify_id    varchar(32) null comment '更新人 id',
    modify_time  datetime    null comment '更新時(shí)間'
)
    comment '倉(cāng)庫(kù)快照';
create table store_snapshot_ext
(
    id             varchar(32) not null comment '主鍵'
        primary key,
    fk_snapshot_id varchar(32) null comment '快照外鍵',
    ext_attr       varchar(32) null comment '擴(kuò)展屬性',
    create_id      varchar(32) null comment '創(chuàng)建人id',
    create_time    datetime    null comment '創(chuàng)建日期',
    modify_id      varchar(32) null comment '更新人 id',
    modify_time    datetime    null comment '更新時(shí)間',
    constraint store_snapshot_ext___fk_snapshot_id
        foreign key (fk_snapshot_id) references store_snapshot (id)
)
    comment '倉(cāng)庫(kù)快照擴(kuò)展屬性';

這里使用 Java 語(yǔ)言模擬并發(fā)情況下對(duì)數(shù)據(jù)的插入:

import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.RandomUtil;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.tea.common.entity.StoreSnapshot;
import org.tea.common.entity.StoreSnapshotExt;
import org.tea.common.mapper.StoreSnapshotExtMapper;
import org.tea.common.mapper.StoreSnapshotMapper;
import javax.annotation.Resource;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = StoreSnapshotApplication.class)
public class StoreSnapBenchTest {
    @Resource
    private PlatformTransactionManager txManager;
    @Resource
    private StoreSnapshotMapper storeSnapshotMapper;
    @Resource
    private StoreSnapshotExtMapper storeSnapshotExtMapper;
    @Test
    public void batchTest() throws InterruptedException {
        Thread[] ts = new Thread[10];
        CountDownLatch startLatch = new CountDownLatch(1);
        CountDownLatch endLatch = new CountDownLatch(ts.length);
        for (int i = 0; i < ts.length; i++) {
            ts[i] = new Thread(() -> {
                DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
                // 設(shè)置事務(wù)隔離級(jí)別為 "可重復(fù)讀"
                definition.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
                TransactionStatus status = txManager.getTransaction(definition);
                try {
                    /**
                    	提高事務(wù)競(jìng)爭(zhēng)的激烈度
                    */
                    startLatch.await();
                    /**
                    	一般情況下,不會(huì)使用如下的循環(huán)方式來(lái)插入數(shù)據(jù),這里這么做的目的是為了
                    	提高事務(wù)的處理時(shí)間,增大鎖的競(jìng)爭(zhēng)激烈度
                    */
                    for (int j = 0; j < 2000; j++) {
                        StoreSnapshot snapshot = new StoreSnapshot();
                        // 使用 UUID 的方式來(lái)引發(fā) Page 分裂
                        snapshot.setId(UUID.randomUUID().toString().replaceAll("-", ""));
                        snapshot.setWarehouseId("warehouse_1");
                        snapshot.setSnapDate(new DateTime());
                        snapshot.init();
                        storeSnapshotMapper.insertSelective(snapshot);
                        StoreSnapshotExt snapshotExt = new StoreSnapshotExt();
                        snapshotExt.setId(UUID.randomUUID().toString().replaceAll("-", ""));
                        snapshotExt.setExtAttr(RandomUtil.randomString(32));
                        // 注意這里的外鍵,后文會(huì)分析這個(gè)外鍵帶來(lái)的一些影響
                        snapshotExt.setFkSnapshotId(snapshot.getId());
                        storeSnapshotExtMapper.insertSelective(snapshotExt);
                    }
                    txManager.commit(status);
                } catch (InterruptedException e) {
                    txManager.rollback(status);
                    throw new RuntimeException(e);
                } finally {
                    endLatch.countDown();
                }
            });
        }
        for (Thread t : ts) {
            t.start();
        }
        startLatch.countDown();
        endLatch.await();
    }
}
@SpringBootApplication
@EnableTransactionManagement
@MapperScan("org.tea.*.mapper")
class StoreSnapshotApplication {
    public static void main(String[] args) {
        SpringApplication.run(StoreSnapshotApplication.class, args);
    }
}

在執(zhí)行完上面的測(cè)試用例后,查看 MySQL InnoDB 的狀態(tài)信息,發(fā)現(xiàn)已經(jīng)出現(xiàn)了死鎖:

------------------------
LATEST DETECTED DEADLOCK
------------------------
2025-08-26 21:01:55 135637563962944
*** (1) TRANSACTION:
TRANSACTION 3866, ACTIVE 2 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 27 lock struct(s), heap size 8312, 220 row lock(s), undo log entries 430
MySQL thread id 17, OS thread handle 135637553456704, query id 8736 localhost ::1 root update
INSERT INTO store_snapshot (create_id, warehouse_id, snap_date, modify_id, id, modify_time, create_time)  VALUES  ('system', 'warehouse_1', '2025-08-26 21:01:54', 'system', '402ce43f650a483eb0c9c5138e50d6f0', '2025-08-26 21:01:54', '2025-08-26 21:01:54')
*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 10 page no 11 n bits 240 index PRIMARY of table `lxh_db`.`store_snapshot` trx id 3866 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 10 page no 20 n bits 160 index PRIMARY of table `lxh_db`.`store_snapshot` trx id 3866 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 5 PHYSICAL RECORD: n_fields 9; compact format; info bits 0
 0: len 30; hex 343033303963393162373166343731633936323164616565643434666363; asc 40309c91b71f471c9621daeed44fcc; (total 32 bytes);
 1: len 6; hex 000000000f14; asc       ;;
 2: len 7; hex 82000001070630; asc       0;;
 3: len 11; hex 77617265686f7573655f31; asc warehouse_1;;
 4: len 5; hex 99b7755074; asc   uPt;;
 5: len 6; hex 73797374656d; asc system;;
 6: len 5; hex 99b7755074; asc   uPt;;
 7: len 6; hex 73797374656d; asc system;;
 8: len 5; hex 99b7755074; asc   uPt;;
*** (2) TRANSACTION:
TRANSACTION 3860, ACTIVE 3 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 46 lock struct(s), heap size 24696, 1258 row lock(s), undo log entries 2474
MySQL thread id 11, OS thread handle 135637890971200, query id 12330 localhost ::1 root update
INSERT INTO store_snapshot (create_id, warehouse_id, snap_date, modify_id, id, modify_time, create_time)  VALUES  ('system', 'warehouse_1', '2025-08-26 21:01:55', 'system', '917f3578682c467384e520fd6c00b86d', '2025-08-26 21:01:55', '2025-08-26 21:01:55')
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 10 page no 20 n bits 160 index PRIMARY of table `lxh_db`.`store_snapshot` trx id 3860 lock_mode X locks gap before rec
Record lock, heap no 3 PHYSICAL RECORD: n_fields 9; compact format; info bits 0
 0: len 30; hex 336661623363323037333232346465666263363762333630363763623666; asc 3fab3c2073224defbc67b36067cb6f; (total 32 bytes);
 1: len 6; hex 000000000f14; asc       ;;
 2: len 7; hex 82000000932d80; asc      - ;;
 3: len 11; hex 77617265686f7573655f31; asc warehouse_1;;
 4: len 5; hex 99b7755076; asc   uPv;;
 5: len 6; hex 73797374656d; asc system;;
 6: len 5; hex 99b7755076; asc   uPv;;
 7: len 6; hex 73797374656d; asc system;;
 8: len 5; hex 99b7755076; asc   uPv;;
Record lock, heap no 4 PHYSICAL RECORD: n_fields 9; compact format; info bits 0
 0: len 30; hex 336664326234626538643463343338326130303764383431366364613536; asc 3fd2b4be8d4c4382a007d8416cda56; (total 32 bytes);
 1: len 6; hex 000000000f14; asc       ;;
 2: len 7; hex 82000000903800; asc      8 ;;
 3: len 11; hex 77617265686f7573655f31; asc warehouse_1;;
 4: len 5; hex 99b7755076; asc   uPv;;
 5: len 6; hex 73797374656d; asc system;;
 6: len 5; hex 99b7755076; asc   uPv;;
 7: len 6; hex 73797374656d; asc system;;
 8: len 5; hex 99b7755076; asc   uPv;;
Record lock, heap no 5 PHYSICAL RECORD: n_fields 9; compact format; info bits 0
 0: len 30; hex 343033303963393162373166343731633936323164616565643434666363; asc 40309c91b71f471c9621daeed44fcc; (total 32 bytes);
 1: len 6; hex 000000000f14; asc       ;;
 2: len 7; hex 82000001070630; asc       0;;
 3: len 11; hex 77617265686f7573655f31; asc warehouse_1;;
 4: len 5; hex 99b7755074; asc   uPt;;
 5: len 6; hex 73797374656d; asc system;;
 6: len 5; hex 99b7755074; asc   uPt;;
 7: len 6; hex 73797374656d; asc system;;
 8: len 5; hex 99b7755074; asc   uPt;;
Record lock, heap no 93 PHYSICAL RECORD: n_fields 9; compact format; info bits 0
 0: len 30; hex 336664396165326435663738346237656161346134323336643031643366; asc 3fd9ae2d5f784b7eaa4a4236d01d3f; (total 32 bytes);
 1: len 6; hex 000000000f14; asc       ;;
 2: len 7; hex 82000000a81784; asc        ;;
 3: len 11; hex 77617265686f7573655f31; asc warehouse_1;;
 4: len 5; hex 99b7755077; asc   uPw;;
 5: len 6; hex 73797374656d; asc system;;
 6: len 5; hex 99b7755077; asc   uPw;;
 7: len 6; hex 73797374656d; asc system;;
 8: len 5; hex 99b7755077; asc   uPw;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 10 page no 11 n bits 240 index PRIMARY of table `lxh_db`.`store_snapshot` trx id 3860 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;
*** WE ROLL BACK TRANSACTION (1)
------------
TRANSACTIONS
------------

可以看到,在日志中,事務(wù) 3866 持有一個(gè)頁(yè)號(hào)為 11,supremum (相當(dāng)于雙鏈表的尾部啞節(jié)點(diǎn)) 的間隙鎖,在等待頁(yè)號(hào)為 20,主鍵為 40309c91b71f471c9621daeed44fcc 的間隙鎖;同時(shí),事務(wù) 3860 持有頁(yè)號(hào)為 20,主鍵為 40309c91b71f471c9621daeed44fcc 的間隙鎖,同時(shí)在等待頁(yè)號(hào)為 10supremum 的間隙鎖。這兩個(gè)事務(wù)構(gòu)成了循環(huán)回路,并且在相互等待,因此形成了死鎖,具體的圖示如下所示:

具體分析

查詢(xún)語(yǔ)句的加鎖

MySQL 的的一般 SELECT 語(yǔ)句在非串行化隔離級(jí)別下是通過(guò)一致性讀的方式進(jìn)行讀取,本身不會(huì)對(duì)記錄加鎖,但是在存在外鍵約束的情況下,依舊會(huì)對(duì)關(guān)聯(lián)的外鍵約束記錄上加上 S 型的記錄鎖,如果關(guān)聯(lián)的外鍵約束沒(méi)有被找到,在"可重復(fù)讀"的隔離級(jí)別下,會(huì)在外鍵記錄附近加上間隙鎖

由于這里插入語(yǔ)句的外鍵都能被找到,因此這里的外鍵不是產(chǎn)生間隙鎖的原因

INSERT 語(yǔ)句的加鎖

單純的 INSERT 語(yǔ)句在插入時(shí)加上的是一種特殊的記錄鎖,不同事務(wù)的插入意向鎖不會(huì)相互阻塞,但是在插入的記錄行的所處位置存在間隙鎖的情況下,會(huì)為當(dāng)前的 INSERT 記錄加上 插入意向鎖

在主鍵重復(fù)的情況下,隔離級(jí)別為 "可重復(fù)讀" 或 "串行化" 的情況下,會(huì)為插入的記錄加上 S 型的 Next-Key 鎖。在這種情況下,如果原來(lái)待插入行的事務(wù)回滾了,由于本身持有的 S 型鎖無(wú)法再獲取到獨(dú)占鎖,就有可能會(huì)引發(fā)死鎖[2]。

由于這里的主鍵都是 UUID,不存在重復(fù)主鍵,并且結(jié)合相關(guān)的日志信息,并不是由于單純的 INSERT 語(yǔ)句導(dǎo)致的死鎖

Page 的分裂

當(dāng)插入的記錄的主鍵不連續(xù)時(shí),MySQL 為了維護(hù)聚簇索引的順序,可能會(huì)引發(fā)頁(yè)的分裂。在事務(wù)隔離級(jí)別為 "可重復(fù)讀"或"串行化" 的情況下,對(duì)分裂的數(shù)據(jù)進(jìn)行遷移的過(guò)程中,相當(dāng)于對(duì)數(shù)據(jù)執(zhí)行了更新的操作,按照 MySQL 對(duì)于 UPDATE 語(yǔ)句的加鎖情況[2],會(huì)在記錄上加上 Next-Key (記錄鎖和間隙鎖),因此事務(wù) 3860會(huì)持有記錄40309c91b71f471c9621daeed44fcc的間隙鎖;而為了防止在分裂維護(hù)過(guò)程中重新插入數(shù)據(jù),可能不得不為相關(guān)頁(yè)記錄的 supremum 加上間隙鎖,以維護(hù)頁(yè)分裂的執(zhí)行過(guò)程

這個(gè)過(guò)程可能如下:

插入記錄前:

插入記錄導(dǎo)致頁(yè)分裂后

注意: 這里關(guān)于分頁(yè)而產(chǎn)生的間隙鎖為實(shí)際實(shí)驗(yàn)推斷,并無(wú)實(shí)際文檔與之關(guān)聯(lián)。當(dāng)事務(wù)隔離級(jí)別為 "讀提交" 或插入的記錄的主鍵存在順序時(shí),都不會(huì)出現(xiàn)上文描述的死鎖出現(xiàn)

解決方案

實(shí)際上,如果事務(wù)執(zhí)行速度特別快,并且在并發(fā)量不高的情況下,這種類(lèi)型的死鎖很難被檢測(cè)到,因?yàn)樾枰幚淼氖聞?wù)跨多個(gè)頁(yè),并且需要關(guān)聯(lián)到兩個(gè)不同頁(yè)的鎖本身就很難。因此,將逐行的 INSERT 語(yǔ)句替換為批量提交后也可以很大程度上解決這一問(wèn)題

為了盡可能地避免這一類(lèi)問(wèn)題,推薦的一些方案如下:

  • 如果沒(méi)有特殊必要,可以使用隔離級(jí)別較低的事務(wù)隔離級(jí)別,因?yàn)檫@樣可以減少實(shí)際事務(wù)處理過(guò)程中鎖的數(shù)量,降低鎖沖突的可能性
  • 盡量使用有序的主鍵,不管是從性能上還是實(shí)際業(yè)務(wù)角度,都沒(méi)有理由選擇 UUID 的理由
  • 如果可以,適當(dāng)減少事務(wù)的粒度,如:將一個(gè)大事務(wù)分成幾個(gè)小事務(wù),在性能和一致性上做一定的權(quán)衡

參考資料:

[1] https://mp.weixin.qq.com/s/9LRFYGquXWpMCeyAonNcMQ

[2] https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html

到此這篇關(guān)于MySQL INSERT 導(dǎo)致的死鎖分析的文章就介紹到這了,更多相關(guān)mysql insert死鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Mysql常用基準(zhǔn)測(cè)試命令總結(jié)

    Mysql常用基準(zhǔn)測(cè)試命令總結(jié)

    在本篇文章中我們給大家分享了關(guān)于Mysql常用基準(zhǔn)測(cè)試命令的總結(jié)內(nèi)容,有需要的讀者們可以學(xué)習(xí)下。
    2018-10-10
  • mysql中如何查看表空間

    mysql中如何查看表空間

    這篇文章主要介紹了mysql中如何查看表空間問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • 數(shù)據(jù)庫(kù)中update與delete使用表別名的深入研究

    數(shù)據(jù)庫(kù)中update與delete使用表別名的深入研究

    本文介紹了UPDATE和DELETE?SQL語(yǔ)句在Oracle,?SQLite,?PostgreSQL,?MYSQL,?mariadb等數(shù)據(jù)庫(kù)系統(tǒng)中的執(zhí)行差異,文中通過(guò)代碼介紹的非常詳細(xì),對(duì)數(shù)據(jù)庫(kù)開(kāi)發(fā)和維護(hù)人員有指導(dǎo)意義,需要的朋友可以參考下
    2024-10-10
  • MySQL插入時(shí)間戳字段的值實(shí)現(xiàn)

    MySQL插入時(shí)間戳字段的值實(shí)現(xiàn)

    在MySQL中,我們經(jīng)常會(huì)遇到需要插入時(shí)間戳字段的情況,包括使用NOW()函數(shù)插入當(dāng)前時(shí)間戳,使用FROM_UNIXTIME()插入指定時(shí)間戳,本文就來(lái)介紹一下,感興趣的可以了解一下
    2024-09-09
  • Centos7使用yum安裝MySQL及實(shí)現(xiàn)遠(yuǎn)程連接的方法

    Centos7使用yum安裝MySQL及實(shí)現(xiàn)遠(yuǎn)程連接的方法

    因?yàn)镸ySQL被Oracle收購(gòu),目前推薦使用mariadb數(shù)據(jù)庫(kù)。下面通過(guò)本文給大家分享Centos7使用yum安裝MySQL及實(shí)現(xiàn)遠(yuǎn)程連接的方法,感興趣的朋友一起看看吧
    2017-07-07
  • MySQL如何構(gòu)建數(shù)據(jù)表索引

    MySQL如何構(gòu)建數(shù)據(jù)表索引

    索引是對(duì)數(shù)據(jù)庫(kù)表中一列或多列的值進(jìn)行排序的一種結(jié)構(gòu),使用索引可快速訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)表中的特定信息。如果想按特定職員的姓來(lái)查找他或她,則與在表中搜索所有的行相比,索引有助于更快地獲取信息。本文將通過(guò)一個(gè)案例帶領(lǐng)你理解索引的概念
    2021-05-05
  • 使用MySQL從JSON字符串提取數(shù)據(jù)的方法詳解

    使用MySQL從JSON字符串提取數(shù)據(jù)的方法詳解

    在現(xiàn)代數(shù)據(jù)庫(kù)管理中,JSON 格式因其靈活性而廣泛使用,然而,當(dāng)數(shù)據(jù)存儲(chǔ)在 JSON 中時(shí),我們經(jīng)常需要將其轉(zhuǎn)換為更易于處理的格式,本篇文章將通過(guò)一個(gè)具體的 SQL 查詢(xún)示例,展示如何從存儲(chǔ)在 MySQL 中的 JSON 字符串提取數(shù)據(jù)并重新格式化,需要的朋友可以參考下
    2024-10-10
  • MySQL關(guān)閉SSL的簡(jiǎn)單方法

    MySQL關(guān)閉SSL的簡(jiǎn)單方法

    這篇文章主要介紹了MySQL關(guān)閉SSL的簡(jiǎn)單方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • MySQL中的多表聯(lián)合查詢(xún)功能操作

    MySQL中的多表聯(lián)合查詢(xún)功能操作

    這篇文章主要介紹了MySQL中的多表聯(lián)合查詢(xún)功能操作,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-02-02
  • MYSQL?增加從庫(kù)方式介紹

    MYSQL?增加從庫(kù)方式介紹

    這篇文章主要介紹了MYSQL?增加從庫(kù)方式介紹,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下,希望對(duì)你的學(xué)習(xí)有所幫助
    2022-08-08

最新評(píng)論

略阳县| 那坡县| 海丰县| 昌平区| 武宁县| 昆山市| 永新县| 弥勒县| 鸡西市| 孟连| 娱乐| 六盘水市| 江达县| 宝山区| 寿阳县| 城固县| 肃南| 白城市| 桃园市| 三明市| 酉阳| 射洪县| 晋中市| 巴林左旗| 盐边县| 梅河口市| 名山县| 武穴市| 延川县| 延边| 靖江市| 长岭县| 白河县| 通海县| 若尔盖县| 岳池县| 开鲁县| 陵川县| 霸州市| 巴彦淖尔市| 宝丰县|