목차
환경을 준비하세요
INSERT 문을 잠그는 방법
死锁要出场了
怎么解决这个死锁问题?
데이터 베이스 MySQL 튜토리얼 두 개의 INSERT 문으로 인해 발생하는 교착 상태에 대해 이야기해 보겠습니다.

두 개의 INSERT 문으로 인해 발생하는 교착 상태에 대해 이야기해 보겠습니다.

Feb 10, 2022 pm 02:49 PM
mysql

이 글은 MySQL의 교착상태와 관련된 문제를 주로 소개하며, 두 개의 동일한 INSERT 문으로 인해 발생하는 교착상태에 대한 관련 지식을 소개합니다.

두 개의 INSERT 문으로 인해 발생하는 교착 상태에 대해 이야기해 보겠습니다.

두 개의 동일한 INSERT 문이 실제로 교착 상태를 일으켰습니다. 이것이 인간 본성의 왜곡인가요, 아니면 도덕성 상실인가요? 한숨을 쉬지 않을 수 없습니다. 젠장! 이것도 교착상태로 이어질 수 있고, 그러다가 눈물을 글썽이며 무기력하게 비즈니스 코드가 바뀌게 된다.

자, 왜 두 개의 동일한 INSERT 문이 교착 상태를 일으킬 수 있는지 자세히 분석하기 전에 먼저 몇 가지 기본 지식을 소개하겠습니다.

환경을 준비하세요

스토리의 원활한 전개를 위해 수없이 사용되었던 새로운 Hero 테이블을 만듭니다:

CREATE TABLE hero (
    number INT AUTO_INCREMENT,
    name VARCHAR(100),
    country varchar(100),
    PRIMARY KEY (number),
    UNIQUE KEY uk_name (name)
) Engine=InnoDB CHARSET=utf8;

그런 다음 이 테이블에 몇 가지 레코드를 삽입합니다:

INSERT INTO hero VALUES
    (1, 'l刘备', '蜀'),
    (3, 'z诸葛亮', '蜀'),
    (8, 'c曹操', '魏'),
    (15, 'x荀彧', '魏'),
    (20, 's孙权', '吴');

이제 Hero 테이블에는 두 개의 인덱스가 있습니다. ( 고유 보조 인덱스, 클러스터형 인덱스) 도식은 다음과 같습니다.

두 개의 INSERT 문으로 인해 발생하는 교착 상태에 대해 이야기해 보겠습니다.

INSERT 문을 잠그는 방법

"MySQL 실행 방법: 기본부터 MySQL 이해"를 읽어본 친구는 알아야 할, INSERT 문은 일반 실행 중에 잠금 구조를 생성하지 않습니다. 이는 클러스터형 인덱스 레코드와 함께 제공되는 숨겨진 열을 암시적 잠금으로 사용하여 레코드를 보호합니다.

그러나 일부 특별한 시나리오에서는 INSERT 문이 여전히 잠금 구조를 생성합니다. 나열해 보겠습니다.

1. 삽입할 다음 레코드가 다른 트랜잭션에 의해 간격 잠금에 추가된 경우

Every. time 새로운 레코드를 삽입할 때 다음 삽입할 레코드에 gap lock이 추가되었는지 확인해야 하며, gap lock이 추가된 경우 INSERT 문을 차단하고 삽입 의도 잠금을 생성해야 한다.

예를 들어 Hero 테이블의 경우 트랜잭션 T1은 REPEATABLE READ(향후에는 RR, READ COMMITTED도 향후 RC라고도 함) 격리 수준에서 실행되고 다음 명령문이 실행됩니다.

# 事务T1
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM hero WHERE number < 8 FOR UPDATE;
+--------+------------+---------+
| number | name       | country |
+--------+------------+---------+
|      1 | l刘备      | 蜀      |
|      3 | z诸葛亮    | 蜀      |
+--------+------------+---------+
2 rows in set (0.02 sec)

이 문은 기본 키 값이 1, 3, 8인 세 개의 레코드가 모두 X 유형의 다음 키 잠금으로 추가됩니다. 믿을 수 없다면 SHOW ENGINE INNODB STATUS 문을 사용해 보겠습니다. 잠금 상황을 보세요. 그림에서 화살표가 가리키는 레코드는 숫자 값이 8인 레코드입니다. 레코드:

두 개의 INSERT 문으로 인해 발생하는 교착 상태에 대해 이야기해 보겠습니다.

팁:

SELECT, DELETE, UPDATE 문을 잠그는 방법은 이미 나와 있습니다. 이전 기사에서 분석했으므로 여기서는 자세히 다루지 않겠습니다.

이때, 트랜잭션 T2는 기본 키 값이 4인 클러스터형 인덱스 레코드를 삽입하려고 합니다. 레코드를 삽입하기 전에 T2는 먼저 페이지에서 기본 키 값이 4인 클러스터형 인덱스 레코드의 위치를 ​​찾아야 합니다. 키 값이 4인 다음 레코드의 주요 기본 키 값은 8이고, 기본 키 값이 8인 클러스터형 인덱스 레코드에 갭 잠금이 추가되었음을 확인합니다(다음 키 잠금에는 형식적인 레코드 잠금이 포함되며 gap 잠금) 이후 트랜잭션 1 차단 상태로 진입하여 삽입 의도 잠금 유형의 잠금 구조를 생성해야 합니다.

트랜잭션 T2에서 INSERT 문을 실행하여 확인합니다.

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO hero VALUES(4, &#39;g关羽&#39;, &#39;蜀&#39;);

이제 T2는 차단 상태에 진입합니다. SHOW ENGINE INNODB STATUS를 사용하여 잠금 상황을 확인합니다.

두 개의 INSERT 문으로 인해 발생하는 교착 상태에 대해 이야기해 보겠습니다.

기본이 되는 것을 볼 수 있습니다. T2의 키 값은 8 입니다. 클러스터형 인덱스 레코드에 삽입 의도 잠금이 추가되고(즉, lock_mode X는 화살표가 가리키는 Rec 삽입 의도 전 간격을 잠급니다) 대기 상태입니다.

확인 후 코드에서 어떻게 구현되는지 살펴보겠습니다.

두 개의 INSERT 문으로 인해 발생하는 교착 상태에 대해 이야기해 보겠습니다.

lock_rec_insert_check_and_lock 함수는 다른 트랜잭션이 이 INSERT 삽입을 방지하는지 확인하는 데 사용됩니다. 그렇다면 이 트랜잭션은 차단됩니다. 또 다른 트랜잭션은 삽입 의도 잠금을 생성하기 위해 Gap 잠금 레코드를 추가합니다. 구체적인 프로세스는 다음과 같습니다.

두 개의 INSERT 문으로 인해 발생하는 교착 상태에 대해 이야기해 보겠습니다.

팁:

lock_rec_other_has_constricting 함수를 사용하여 이번에 획득할 잠금과 기존 잠금을 ​​감지합니다. 기록 충돌이 있는 경우 관심 있는 학생들이 살펴볼 수 있습니다.

2. 중복 키가 발생한 경우

새 레코드 삽입 시 페이지에 있는 기존 레코드의 기본 키 또는 고유 보조 인덱스 열이 기본 키 또는 고유 보조 인덱스 열과 다른 것으로 발견된 경우 삽입할 레코드의 인덱스 열 인덱스 열 값은 동일합니다(단, 여러 레코드의 고유한 보조 인덱스 열 값이 동시에 NULL일 수 있습니다. 여기서는 이러한 상황을 고려하지 않습니다). 이번에는 새 레코드를 삽입하는 트랜잭션이 페이지 레코드 잠금에 이미 존재하는 것과 동일한 키 값을 얻습니다.

기본 키 값이 중복된 경우:

  • 격리 수준이 RC보다 크지 않으면 새 레코드를 삽입하는 트랜잭션은 기본 중복이 포함된 기존 클러스터형 인덱스 레코드에 S자 형식 레코드 잠금을 추가합니다. 핵심 가치.

  • 격리 수준이 RR 이상인 경우 새 레코드를 삽입하는 트랜잭션은 중복 기본 키 값을 사용하여 기존 클러스터형 인덱스 레코드에 S 유형 다음 키 잠금을 추가합니다.

如果是唯一二级索引列重复,那不论是哪个隔离级别,插入新记录的事务都会给已存在的二级索引列值重复的二级索引记录添加S型next-key锁,再强调一遍,加的是next-key锁!加的是next-key锁!加的是next-key锁!这是rc隔离级别中为数不多的给记录添加gap锁的场景。

小贴士:

本来设计InnoDB的大叔并不想在RC隔离级别引入gap锁,但是由于某些原因,如果不添加gap锁的话,会让唯一二级索引中出现多条唯一二级索引列值相同的记录,这就违背了UNIQUE约束。所以后来设计InnoDB的大叔就很不情愿的在RC隔离级别也引入了gap锁。

我们也来做一个实验,现在假设上边的T1和T2都回滚了,现在将隔离级别调至RC,重新开启事务进行测试。

mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
Query OK, 0 rows affected (0.01 sec)
# 事务T1
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO hero VALUES(30, &#39;x荀彧&#39;, &#39;魏&#39;);
ERROR 1062 (23000): Duplicate entry &#39;x荀彧&#39; for key &#39;uk_name&#39;

然后执行SHOW ENGINE INNODB STATUS语句看一下T1加了什么锁:

두 개의 INSERT 문으로 인해 발생하는 교착 상태에 대해 이야기해 보겠습니다.

可以看到即使现在T1的隔离级别为RC,T1仍然给name列值为'x荀彧'的二级索引记录添加了S型next-key锁(图中红框中的lock mode S)。

如果我们的INSERT语句还带有ON DUPLICATE KEY... 这样的子句,如果遇到主键值或者唯一二级索引列值重复的情况,会对B+树中已存在的相同键值的记录加X型锁,而不是S型锁(不过具体锁的具体类型是和前面描述一样的)。

好了,又到了看代码求证时间了,我们看一下吧:

두 개의 INSERT 문으로 인해 발생하는 교착 상태에 대해 이야기해 보겠습니다.

row_ins_scan_sec_index_for_duplicate是检测唯一二级索引列值是否重复的函数,具体加锁的代码如下所示:

두 개의 INSERT 문으로 인해 발생하는 교착 상태에 대해 이야기해 보겠습니다.

如上图所示,在遇到唯一二级索引列重复的情况时:

  • 1号红框表示对带有ON DUPLICATE ...子句时的处理方案,具体就是添加X型锁。

  • 2号红框表示对正常INSERT语句的处理方案,具体就是添加S型锁。

不过不论是那种情况,添加的lock_typed的值都是LOCK_ORDINARY,表示next-key锁。

在主键重复时INSERT语句的加锁代码我们就不列举了。

3. 外键检查时

当我们向子表中插入记录时,我们分两种情况讨论:

  • 当子表中的外键值可以在父表中找到时,那么无论当前事务是什么隔离级别,只需要给父表中对应的记录添加一个S型正经记录锁就好了。

  • 当子表中的外键值在父表中找不到时:那么如果当前隔离级别不大于RC时,不对父表记录加锁;当隔离级别不小于RR时,对父表中该外键值所在位置的下一条记录添加gap锁。

死锁要出场了

好了,基础知识预习完了,该死锁出场了。

看下边这个平平无奇的INSERT语句:

INSERT INTO hero(name, country) VALUES(&#39;g关羽&#39;, &#39;蜀&#39;), (&#39;d邓艾&#39;, &#39;魏&#39;);

这个语句用来插入两条记录,不论是在RC,还是RR隔离级别,如果两个事务并发执行它们是有一定几率触发死锁的。为了稳定复现这个死锁,我们把上边一条语句拆分成两条语句:

INSERT INTO hero(name, country) VALUES(&#39;g关羽&#39;, &#39;蜀&#39;);
INSERT INTO hero(name, country) VALUES(&#39;d邓艾&#39;, &#39;魏&#39;);

拆分前和拆分后起到的作用是相同的,只不过拆分后我们可以人为的控制插入记录的时机。如果T1和T2的执行顺序是这样的:

두 개의 INSERT 문으로 인해 발생하는 교착 상태에 대해 이야기해 보겠습니다.

也就是:

  • T1先插入name值为g关羽的记录,可以插入成功,此时对应的唯一二级索引记录被隐式锁保护,我们执行SHOW ENGINE INNODB STATUS语句,发现啥一个行锁(row lock)都没有(因为SHOW ENGINE INNODB STATUS不显示隐式锁):

두 개의 INSERT 문으로 인해 발생하는 교착 상태에 대해 이야기해 보겠습니다.

  • 接着T2也插入name值为g关羽的记录。由于T1已经插入name值为g关羽的记录,所以T2在插入二级索引记录时会遇到重复的唯一二级索引列值,此时T2想获取一个S型next-key锁,但是T1并未提交,T1插入的name值为g关羽的记录上的隐式锁相当于一个X型正经记录锁(RC隔离级别),所以T2向获取S型next-key锁时会遇到锁冲突,T2进入阻塞状态,并且将T1的隐式锁转换为显式锁(就是帮助T1生成一个正经记录锁的锁结构)。这时我们再执行SHOW ENGINE INNODB STATUS语句:

두 개의 INSERT 문으로 인해 발생하는 교착 상태에 대해 이야기해 보겠습니다.

可见,T1持有的name值为g关羽的隐式锁已经被转换为显式锁(X型正经记录锁,lock_mode X locks rec but not gap);T2正在等待获取一个S型next-key锁(lock mode S waiting)。

  • 接着T1再插入一条name值为d邓艾的记录。在插入一条记录时,会在页面中先定位到这条记录的位置。在插入name值为d邓艾的二级索引记录时,发现现在页面中的记录分布情况如下所示:

두 개의 INSERT 문으로 인해 발생하는 교착 상태에 대해 이야기해 보겠습니다.

很显然,name值为'd邓艾'的二级索引记录所在位置的下一条二级索引记录的name值应该是'g关羽'(按照汉语拼音排序)。那么在T1插入name值为d邓艾的二级索引记录时,就需要看一下name值为'g关羽'的二级索引记录上有没有被别的事务加gap锁。

有同学想说:目前只有T2想在name值为'g关羽'的二级索引记录上添加S型next-key锁(next-key锁包含gap锁),但是T2并没有获取到锁呀,目前正在等待状态。那么T1不是能顺利插入name值为'g关羽'的二级索引记录么?

我们看一下执行结果:

# 事务T2
mysql> INSERT INTO hero(name, country) VALUES(&#39;g关羽&#39;, &#39;蜀&#39;);
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

很显然,触发了一个死锁,T2被InnoDB回滚了。

这是为啥呢?T2明明没有获取到name值为'g关羽'的二级索引记录上的S型next-key锁,为啥T1还不能插入入name值为d邓艾的二级索引记录呢?

这我们还得回到代码上来,看一下插入新记录时是如何判断锁是否冲突的:

두 개의 INSERT 문으로 인해 발생하는 교착 상태에 대해 이야기해 보겠습니다.

看一下画红框的注释,意思是:只要别的事务生成了一个显式的gap锁的锁结构,不论那个事务已经获取到了该锁(granted),还是正在等待获取(waiting),当前事务的INSERT操作都应该被阻塞。

回到我们的例子中来,就是T2已经在name值为'g关羽'的二级索引记录上生成了一个S型next-key锁的锁结构,虽然T2正在阻塞(尚未获取锁),但是T1仍然不能插入name值为d邓艾的二级索引记录。

这样也就解释了死锁产生的原因:

  • T1在等待T2释放name值为'g关羽'的二级索引记录上的gap锁。

  • T2在等待T1释放name值为'g关羽'的二级索引记录上的X型正经记录锁。

两个事务相互等待对方释放锁,这样死锁也就产生了。

怎么解决这个死锁问题?

两个方案:

  • 方案一:一个事务中只插入一条记录。

  • 方案二:先插入name值为'd邓艾'的记录,再插入name值为'g关羽'的记录

推荐学习:mysql视频教程

위 내용은 두 개의 INSERT 문으로 인해 발생하는 교착 상태에 대해 이야기해 보겠습니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Clothoff.io

Clothoff.io

AI 옷 제거제

Video Face Swap

Video Face Swap

완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제

재무 데이터 저장을위한 MySQL 최적화 재무 데이터 저장을위한 MySQL 최적화 Jul 27, 2025 am 02:06 AM

MySQL은 재무 시스템에 최적화되어야합니다. 1. 1. 소수점 유형을 사용하여 정확성을 보장하기 위해 재무 데이터를 사용해야하며 시간대 문제를 피하기 위해 시간 필드에서 DateTime을 사용해야합니다. 2. 인덱스 디자인은 합리적이어야하며, 인덱스를 구축하기위한 필드의 자주 업데이트를 피하고 쿼리 순서로 인덱스를 결합하고 정기적으로 쓸모없는 색인을 청소하십시오. 3. 트랜잭션을 사용하여 일관성을 보장하고, 거래 세분성을 제어하고, 긴 트랜잭션과 비 코어 운영을 피하고, 비즈니스에 따라 적절한 격리 수준을 선택하십시오. 4. 시간별로 히스토리 데이터를 파티션하고, 콜드 데이터를 보관하고 압축 테이블을 사용하여 쿼리 효율성을 향상시키고 스토리지를 최적화합니다.

실시간 데이터 피드에 대한 MySQL 최적화 실시간 데이터 피드에 대한 MySQL 최적화 Jul 26, 2025 am 05:41 AM

tooptimizemysqlforreal-timedatafeeds, firstchoosetheeNnodBStorageEngineForTransactionsand-levellocking, usememoryorrocksdbfortemporaryData 및 partitionTime-seriesDatabyTime.second, INdexStraticalStralityApplyIndExowhere, Or OrdorMOMN, OR ORDOMUMANGS, ORORTORMOMNS.

클라우드 마이그레이션에 대한 MySQL 데이터베이스 비용-이익 분석 클라우드 마이그레이션에 대한 MySQL 데이터베이스 비용-이익 분석 Jul 26, 2025 am 03:32 AM

MySQL이 클라우드로 이동할 가치가 있는지 여부는 특정 사용 시나리오에 따라 다릅니다. 비즈니스를 빠르게 출시 해야하는 경우 탄력적으로 확장하고 운영 및 유지 보수를 단순화하며 Go-Go-Go Pay-as-Go 모델을 수락 할 수있는 경우 클라우드로 이동하는 것이 그만한 가치가 있습니다. 그러나 데이터베이스가 오랫동안 안정되어 있으면 대기 시간에 민감하거나 규정 준수 제한이 있으면 비용 효율적이지 않을 수 있습니다. 비용 제어 키에는 올바른 공급 업체 및 패키지 선택, 합리적으로 리소스 구성, 예약 인스턴스 사용, 백업 로그 관리 및 쿼리 성능 최적화가 포함됩니다.

객체 수준의 권한으로 MySQL 보안 객체 수준의 권한으로 MySQL 보안 Jul 29, 2025 am 01:34 AM

TosecureMySQLeffectively,useobject-levelprivilegestolimituseraccessbasedontheirspecificneeds.Beginbyunderstandingthatobject-levelprivilegesapplytodatabases,tables,orcolumns,offeringfinercontrolthanglobalprivileges.Next,applytheprincipleofleastprivile

대형 MySQL 테이블을 관리하기위한 모범 사례 대형 MySQL 테이블을 관리하기위한 모범 사례 Aug 05, 2025 am 03:55 AM

대형 테이블을 다룰 때 MySQL 성능 및 유지 보수 가능성은 직면하고 구조 설계, 인덱스 최적화, 테이블 하위 테이블 전략 등을 시작해야합니다. 1. 기본 키 및 색인을 합리적으로 설계해야합니다. 자체 증가 정수를 기본 키로 사용하여 페이지 분할을 줄이는 것이 좋습니다. 오버레이 인덱스를 사용하여 쿼리 효율성을 향상시킵니다. 느린 쿼리 로그를 정기적으로 분석하고 유효하지 않은 인덱스를 삭제하십시오. 2. 파티션 테이블의 합리적 사용 : 시간 범위 및 쿼리 및 유지 보수 효율성을 향상시키기위한 기타 전략에 따른 파티션이지만 분할 및 절단 문제에주의를 기울여야합니다. 3. 분리 및 도서관 분리를 읽고 쓰고 쓰는 것을 고려하십시오 : 읽기 및 쓰기 분리는 메인 라이브러리의 압력을 완화시킵니다. 라이브러리 분리 및 테이블 분리는 많은 양의 데이터가있는 시나리오에 적합합니다. 미들웨어를 사용하고 거래 및 크로스 스토어 쿼리 문제를 평가하는 것이 좋습니다. 초기 계획과 지속적인 최적화가 핵심입니다.

컨텐츠 관리 시스템 (CMS) 용 MySQL 최적화 컨텐츠 관리 시스템 (CMS) 용 MySQL 최적화 Jul 28, 2025 am 03:19 AM

ToimProveMySqlPerformanceForcmsPlatforms Wordpress, FirstImplementAcinglayerUsingLayerUsingPluginSlikerEdisorMemcached, enableMysqlQueryCaching (ifapplicable), anduSepageCingpluginstoservestaticfiles.second, 옵티미즈 QLConcepingInnodBuf

MySQL 데이터베이스 복제 필터 구현 MySQL 데이터베이스 복제 필터 구현 Jul 28, 2025 am 02:36 AM

MySQL 복제 필터링은 기본 라이브러리 또는 슬레이브 라이브러리에서 구성 할 수 있습니다. 메인 라이브러리는 Binlog-Do-DB 또는 Binlog-inignore-DB를 통해 Binlog 생성을 제어하며, 이는 로그 볼륨을 줄이는 데 적합합니다. 데이터 애플리케이션은 복제 -DO-DB, 복제-디자르 -DB, 복제--테이블, 복제-디너 테이블 및 와일드 카드 규칙 복제-야드---테이블 및 복제-야드-원형-테이블에 의해 제어됩니다. 데이터 복구에 더 유연하고 도움이됩니다. 구성 할 때는 규칙의 순서, 크로스 스토어 명세서 동작에주의를 기울여야합니다.

MySQL의 자르기, 삭제 및 하락의 차이점은 무엇입니까? MySQL의 자르기, 삭제 및 하락의 차이점은 무엇입니까? Aug 05, 2025 am 09:39 AM

deletereMovesspecificorAllrows, KeepStableStructure, 허용 롤백 트리거 및 DOSNOTRESETAUTO-Increment; 2.TrUncateQuicklyRemovesAllrows, resetSauto-increment, Most Cases, Disfiretiggers, and KeepstableSthee;

See all articles