트랜잭션이 업데이트될 때; 데이터를 먼저 추가해야 합니다. 테이블 수준 배타적 잠금은 트랜잭션이 끝날 때까지 해제되지 않습니다.
격리 수준 제어를 구현하기 위해 잠금만 사용할 경우 빈번한 잠금 및 잠금 해제가 필요하며 읽기 및 쓰기 충돌이 발생하기 쉽다는 것을 알 수 있습니다(예를 들어 RC 수준에서 트랜잭션 A는 데이터 행 1을 업데이트하고 트랜잭션 B는 데이터를 업데이트합니다) 그런 다음 트랜잭션 A가 커밋되기 전에 데이터 행 1을 읽으려면 트랜잭션 A가 커밋되고 잠금이 해제될 때까지 기다려야 합니다.
잠금 없이 읽기 및 쓰기 충돌 문제를 해결하기 위해 MySQL은 MVCC 메커니즘을 도입했습니다. 자세한 내용은 이전 분석 기사인 데이터베이스의 낙관적 잠금, 비관적 잠금 및 MVCC 이해를 참조하세요.
InnoDB 트랜잭션 격리 수준 구현 원칙
분석을 진행하기 전에 먼저 이해해야 할 몇 가지 개념이 있습니다.
1. 잠금 읽기 및 일관된 비잠금 읽기
잠긴 읽기: transaction , SELECT ... LOCK IN SHARE MODE 및 SELECT ... FOR UPDATE와 같은 읽기를 적극적으로 잠급니다. 행 공유 잠금 및 행 배타적 잠금이 각각 추가됩니다. 잠금 분류는 이전 분석 기사인 알아야 할 MySQL 잠금 분류에서 찾을 수 있습니다.
https://dev.mysql.com/doc/refman/8.0/en/innodb-locking-reads.html
일관적인 비잠금 읽기: InnoDB는 MVCC를 사용하여 트랜잭션 쿼리에 특정 시점을 제공합니다. 데이터베이스 스냅샷. 쿼리는 해당 시점 이전에 커밋된 트랜잭션에 의해 변경된 내용을 볼 수 있지만 이후 또는 커밋되지 않은 트랜잭션(이 트랜잭션 제외)에 의해 변경된 내용은 볼 수 없습니다. 즉, 트랜잭션을 시작한 후 해당 트랜잭션에서 볼 수 있는 데이터는 해당 트랜잭션이 시작된 순간의 데이터이며 이후 다른 트랜잭션의 수정 사항은 이 트랜잭션에서 볼 수 없습니다.
일관적 읽기는 RC 및 RR 격리 수준에서 SELECT 문을 처리하기 위한 InnoDB의 기본 모드입니다. 일관된 비잠금 읽기는 액세스하는 테이블에 잠금을 설정하지 않으므로 테이블에 대해 일관된 비잠금 읽기를 수행하는 동안 다른 트랜잭션이 동시에 이를 읽거나 수정할 수 있습니다.
https://dev.mysql.com/doc/refman/8.0/en/innodb-contant-read.html
2. 현재 읽기 및 스냅샷 읽기
현재 읽기
가 읽혀졌습니다. UPDATE, DELETE, INSERT, SELECT... LOCK IN SHARE MODE, SELECT... FOR UPDATE 이러한 작업은 모두 현재 읽기입니다. 왜 현재 읽기라고 합니까? 즉, 읽을 때 최신 버전의 레코드를 읽는 동시에 다른 동시 트랜잭션이 현재 레코드를 수정할 수 없도록 해야 하며 읽은 레코드가 잠깁니다.
스냅샷 읽기
잠금이 없는 SELECT 작업은 스냅샷 읽기이며, 이는 잠금이 없는 비차단 읽기입니다. 스냅샷 읽기의 전제는 격리 수준입니다. 커밋되지 않은 읽기 및 직렬화된 읽기 수준은 아닙니다. 커밋되지 않은 읽기는 항상 현재 트랜잭션 버전을 준수하는 데이터 행이 아닌 최신 데이터 행을 읽고 직렬화된 읽기는 테이블을 잠그기 때문입니다.
3. 암시적 잠금 및 명시적 잠금
암시적 잠금
InnoDB는 트랜잭션 실행 중에 2단계 잠금 프로토콜을 사용합니다(명시적 잠금을 적극적으로 수행하지 않음):
- 언제든지 수행할 수 있습니다. 잠금, InnoDB는 격리 수준에 따라 필요할 때 자동으로 잠깁니다.
- 잠금은 커밋 또는 롤백이 실행될 때만 해제되며 모든 잠금은 동시에 해제됩니다.
명시적 잠금
위 개념을 이해한 후 다음을 수행해 보겠습니다. InnoDB 트랜잭션이 어떻게 구현되는지 살펴보세요(다음 판독값은 모두 비활성으로 잠긴 선택을 참조함) 事务隔离级别 | 实现方式 |
---|
未提交读(RU) | 事务对当前被读取的数据不加锁,都是当前读;
事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级共享锁,直到事务结束才释放。 | 提交读(RC) | 事务对当前被读取的数据不加锁,且是快照读;
事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁(Record),直到事务结束才释放。 | 可重复读(RR) | 事务对当前被读取的数据不加锁,且是快照读;
事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁(Record,GAP,Next-Key),直到事务结束才释放。
通过间隙锁,在这个级别MySQL就解决了幻读的问题
通过快照,在这个级别MySQL就解决了不可重复读的问题 | 序列化读(S) | 事务在读取数据时,必须先对其加表级共享锁 ,直到事务结束才释放,都是当前读;
事务在更新数据时,必须先对其加表级排他锁 ,直到事务结束才释放。 |
可以看到,InnoDB通过MVCC很好的解决了读写冲突的问题,而且提前一个级别就解决了标准级别下会出现的幻读问题,大大提升了数据库的并发能力。 一些常见误区幻读到底包不包括了delete的情况?不可重复读:前后多次读取一行,数据内容不一致,针对其他事务的update和delete操作。为了解决这个问题,使用行共享锁,锁定到事务结束(也就是RR级别,当然MySQL使用MVCC在RC级别就解决了这个问题) 幻读:当同一个查询在不同时间生成不同的行集合时就是出现了幻读,针对的是其他事务的insert操作,为了解决这个问题,锁定整个表到事务结束(也就是S级别,当然MySQL使用间隙锁在RR级别就解决了这个问题) 网上很多文章提到幻读和提交读的时候,有的说幻读包括了delete的情况,有的说delete应该属于提交读的问题,那到底真相如何呢?我们实际来看下MySQL的官方文档(如下) The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a SELECT ) is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row. https://dev.mysql.com/doc/refman/5.7/en/innodb-next-key-locking.html
可以看到,幻读针对的是结果集前后发生变化,所以看起来delete的情况应该归为幻读,但是我们实际分析下上面列出的标准SQL在RR级别的实现原理就知道,标准SQL的RR级别是会对查到的数据行加行共享锁,所以这时候其他事务想删除这些数据行其实是做不到的,所以在RR下,不会出现因delete而出现幻读现象,也就是幻读不包含delete的情况。 MVCC能解决了幻读问题?网上很多文章会说MVCC或者MVCC+间隙锁解决了幻读问题,实际上MVCC并不能解决幻读问题。如以下的例子: begin;
#假设users表为空,下面查出来的数据为空
select * from users; #没有加锁
#此时另一个事务提交了,且插入了一条id=1的数据
select * from users; #读快照,查出来的数据为空
update users set name='mysql' where id=1;#update是当前读,所以更新成功,并生成一个更新的快照
select * from users; #读快照,查出来id为1的一条记录,因为MVCC可以查到当前事务生成的快照
commit; 로그인 후 복사 可以看到前后查出来的数据行不一致,发生了幻读。所以说只有MVCC是不能解决幻读问题的,解决幻读问题靠的是间隙锁。如下: begin;
#假设users表为空,下面查出来的数据为空
select * from users lock in share mode; #加上共享锁
#此时另一个事务B想提交且插入了一条id=1的数据,由于有间隙锁,所以要等待
select * from users; #读快照,查出来的数据为空
update users set name='mysql' where id=1;#update是当前读,由于不存在数据,不进行更新
select * from users; #读快照,查出来的数据为空
commit;
#事务B提交成功并插入数据 로그인 후 복사 注意,RR级别下想解决幻读问题,需要我们显式加锁,不然查询的时候还是不会加锁的。
原文地址:https://segmentfault.com/a/1190000025156465
作者: X先生
【相关推荐:mysql视频教程】
|