MySQL MVVC 다중 버전 동시성 제어를 구현하는 방법

WBOY
풀어 주다: 2023-05-31 13:14:01
앞으로
1644명이 탐색했습니다.

1. 개요

MVCC(다중 버전 동시성 제어)는 동시성 제어 기술입니다. 데이터베이스 동시성 제어를 구현하는 MVVC는 여러 버전의 데이터 행을 유지 관리하여 수행되는 실행 취소 로그의 버전 체인과 분리될 수 없습니다.

간단히 말하면, 현재 트랜잭션은 다른 트랜잭션에 의해 변경되는 행을 쿼리합니다. (이때 읽으면 더티 읽기가 발생합니다.) 잠그고 기다리는 대신 데이터의 기록 버전을 읽어 응답 시간을 줄입니다.

MVVC는 실행 취소 로그와 읽기 보기라는 두 가지 기술을 통해 구현됩니다.

2. 스냅샷 읽기 및 현재 읽기

MySQL InnoDB에서 MVCC 구현은 주로 데이터베이스 동시성 성능을 향상하고 읽기-쓰기 충돌을 처리하는 더 나은 방법을 사용하므로 읽기-쓰기 충돌이 있는 경우에도 잠금 없음, 비차단 동시 읽기, 이 읽기는 현재 읽기가 아닌 스냅샷 읽기를 나타냅니다. 현재 읽기는 실제로 잠금 작업입니다.

1. 현재 읽기

읽은 기록이 최신 데이터인지 확인하고 다른 동시 트랜잭션이 기록을 수정하는 것을 방지하려면 읽을 때 기록을 잠가야 합니다.

잠긴 읽기를 현재 읽기라고 하며, 데이터의 추가, 삭제, 수정을 먼저 읽어야 합니다. 이 읽기 프로세스도 현재 읽기입니다.

SELECT * FROM t LOCK IN SHARE MODE; # 共享锁
SELECT * FROM t FOR UPDATE; # 排他锁
UPDATE SET t..
로그인 후 복사

2. 스냅샷 읽기

스냅샷 읽기는 데이터 행의 스냅샷 버전을 읽습니다. MySQL에서 일반 select 문(공유 모드에서 업데이트나 잠금이 없는 select 문)은 기본적으로 잠금 없이 스냅샷 읽기를 사용합니다.

SELECT * FROM table WHERE ...
로그인 후 복사

그 이유는 스냅샷을 읽으면 작업 잠금을 방지하고 오버헤드를 줄일 수 있기 때문입니다.

트랜잭션의 격리 수준이 직렬인 경우 스냅샷 읽기는 쓸모가 없으며 현재 읽기 수준으로 저하됩니다.

3. 격리 수준 및 버전 체인 검토

격리 수준:

MySQL의 기본 격리 수준은 반복 불가능 읽기 문제를 해결할 수 있는 반복 읽기 RR입니다. MySQL에서는 팬텀 읽기 문제 해결도 지원합니다. .

환상 독서 문제를 어떻게 해결하나요? 두 가지 방법이 있습니다:

  • 문제를 해결하려면 갭 잠금 및 임시 키 잠금을 사용하십시오. 즉, 이 기간 동안 다른 트랜잭션은 데이터를 삽입할 수 없습니다.

  • MVCC 방법, 잠금이 필요하지 않음, 낮은 소비. (단점 팬텀리딩 문제를 완전히 해결하지 못한다).

실행 취소 로그 버전 체인:

InnoDB에 해당하는 클러스터형 인덱스의 각 레코드에는 두 개의 필수 숨겨진 필드가 포함됩니다.

  • trx_id: 트랜잭션이 특정 클러스터형 인덱스에 적용될 때마다 레코드가 수정되면 해당 거래의 거래 ID는 trx_id 숨겨진 열에 할당됩니다.

  • roll_pointer: 롤백 포인터. 데이터가 수정될 때마다 이전 데이터가 실행 취소 로그에 저장됩니다. 이 포인터 필드를 롤백 포인터라고 합니다. , 이 포인터를 통해 수정 전의 데이터를 확인할 수 있습니다.

예:

ID 8의 트랜잭션이 데이터 조각을 생성한 후 레코드의 다이어그램은 대략 다음과 같습니다.

MySQL MVVC 다중 버전 동시성 제어를 구현하는 방법

ID 10과 20의 두 트랜잭션이 각각 이 레코드를 수행한다고 가정합니다. 작업 과정은 다음과 같습니다:

每个修改都会生成一个undo log日志,并与其他日志相互链接形成版本链,从而呈现出该条数据的图示

MySQL MVVC 다중 버전 동시성 제어를 구현하는 방법

每个版本中还包含生成该版本时对应的事务id 。

四、Read View

有了undo log就可以读取到记录的历史版本,那么在什么情况下,读取哪个版本的记录呢?这就用到了Read View,它帮我们解决了行的可见性问题。

读视图是指在使用MVCC机制进行快照读操作时产生的事务视图。这个视图是对当前数据库中所有活跃的、尚未提交的事务ID列表进行拍照的。

1.实现原理

四种隔离级别里,读未提交和串行化是不会使用MVVC的,因为读未提交直接读取某个数据的最新数据即可,串行化是通过加锁来读的。

读已提交和可重复读都必须保证读到的数据都是其他事务提交了的,所以,其他事务修改了数据但是还未提交,我们不能够访问该数据,但可以通过MVVC机制读取该记录的历史版本,核心问题就是需要判断版本链中的哪条历史版本是当前事务可见的,这也是ReadView要解决的问题。

Read View包含4个比较重要的内容:

  • creator_trx_id:创建这个Read View的事务id,Read View和事务是一一对应的。

当事务对表中的记录作出修改时,才会分配一个事务ID,否则如果事务仅进行读取操作,则该事务的ID默认为0。

  • trx_ids:表示在生成Read View时当前系统中活跃的事务id列表。提交了的事务不在其中。

  • up_limit_id:活跃的事务中最小的事务id。

  • low_limit_id:表示生成Read View时系统应该分配给下一个事务的id值,同样也表示系统中最大的事务id值。

请注意,低限制事务ID不一定是trx_ids中的最大值,因为事务ID是按递增顺序分配的。例如,有三个事务的id分别为1、2、5,其中id为5的事务提交了。那么一个新的读事务在生成ReadView时, trx_ids就包括1和2,up_limit_id的值就是1,low_limit_id的值就是6。

MySQL MVVC 다중 버전 동시성 제어를 구현하는 방법

2.Read View规则

MySQL MVVC 다중 버전 동시성 제어를 구현하는 방법

版本链

当某个事务有了Read View,访问某条记录时,需要按照下面的步骤判断该记录的哪个版本可见:

  • 如果该版本记录的trx_id和Read View的creator_trx_id相同,意味着该版本的记录是由当前事务修改的,因此该版本可以被当前事务访问

  • 如果该版本记录的trx_id小于Read View的up_limit_id,证明当前事务生成Read View时,此事务已经提交了,所以当前事务可以读取该版本。

  • 如果该版本的trx_id大于等于low_limit_id,证明生成该版本的事务在当前事务生成Read View之后才开启,所以该版本不可以被当前事务访问。

  • 如果被访问版本的trx_id属性值在ReadView的up_limit_id和low_limit_id之间,那就需要判断一下trx_id属性值是不是在trx_ids列表中,如果不在的话才能访问,否则不能访问。

3.整体流程

了解了这些概念之后,我们来看下当查询一条记录的时候,系统如何通过MVCC找到它:

  • 首先获取事务自己的版本号,也就是事务ID;

  • 获取 ReadView;

  • 查询得到的数据,然后与 ReadView 中的事务版本号进行比较;

  • 如果不符合 ReadView 规则,就需要从Undo Log中获取历史快照;

  • 最后返回符合规则的数据。

在隔离级别为读已提交时,一个事务中的每一次SELECT查询都会重新获取一次Read View,而可重复读是第一SELECT操作才会生成Read View,之后的查询操作复用这一个。

导致这两种的差距是因为:可重复读要保证一个事务中相同的SELECT读取的内容是相同的。

MySQL MVVC 다중 버전 동시성 제어를 구현하는 방법

五、举例

1.READ

COMMITTED隔离级别下

现在有两个事务id分别为10、20的事务在执行:

-- id为10的事务
begin;
update t set name='李四' where id=1;
update t set name='王五' where id=1;
-- id为20的事务
更新其他行的数据
로그인 후 복사

此刻,表中id为1的记录得到的版本链表如下所示:

MySQL MVVC 다중 버전 동시성 제어를 구현하는 방법

此时新来一个事务执行如下操作:

begin;
select * from t where id=1;
-- 事务10、20未提交
로그인 후 복사

查询到的结果为张三。

具体的过程如下:

  • 在执行select语句前,先生成一个Read View,Read View的creator_trx_id为0,trx_ids列表的内容是[10,20],up_limit_id为10,low_limit_id为21。

  • 查询name为王五的最新版本的记录,按规则进行对比,因为trx_id为10,10刚好是trx_ids中的记录,所以这条记录对当前事务不可见,根据回滚指针得到下一个版本

  • 下一个版本name为李四,也不行

  • 继续找到name为张三的版本,trx_id为8,8小于up_limit_id,所以该版本对当前事务可见,得到最终结果

接下来,再将id为10的事务进行commit提交。然后id为20的事务来更新记录:

begin;
-- id为20的事务
update t set name='赵六' where id=1;
update t set name='钱七' where id=1;
로그인 후 복사

此时版本链更新为:

MySQL MVVC 다중 버전 동시성 제어를 구현하는 방법

再到刚才使用READ COMMITTED隔离级别的事务中继续查找这个id 为1的记录,得到的结果为name=王五的那条记录。执行过程如下:

  • 生成Read View,Read View的creator_trx_id为0,trx_ids列表的内容是[20],up_limit_id为20,low_limit_id为21。

  • 因为前两个版本的记录trx_id为20,存在trx_ids中,所以跳过

  • 到第三条记录时,trx_id为10,小于20,可以读取,所以最终结果为王五

注意:READ COMMITTED,每次读取数据前都生成一个新的ReadView。

2.REPEATABLE READ隔离级别下

假如此时id为10的事务和id为20的事务正在修改,都未提交,修改内容和前面的一样,但是还未提交,此时当前事务做一个查询。

MySQL MVVC 다중 버전 동시성 제어를 구현하는 방법

步骤为:

  • 生成Read View,Read View的creator_trx_id为0,trx_ids列表的内容是[10,20],up_limit_id为10,low_limit_id为21。

  • trx_id为10和20的都不满足要求

  • 最后查找到name为张三的历史版本的数据

此时,id为10的记录提交事务。

MySQL MVVC 다중 버전 동시성 제어를 구현하는 방법

当前事务又需要select id为1的记录,步骤为:

  • 因为是可重复读,且第一次select已经生成过Read View了,所有会复用它,不重新生成。

  • 所以trx_id为10和20的记录依旧不符合规则,最终得到的数据还是张三,符合可重复读的规范

注意:REPEATABLE READ,每次读取都复用第一次生成的Read View

3.如何解决幻读

假设现在有一条数据,id为1

MySQL MVVC 다중 버전 동시성 제어를 구현하는 방법

当前活跃的事务有10和20。

此时当前事务启动了,执行如下SQL语句:

begin;
select * from student where id>=1;
로그인 후 복사

在开始前生成Read View,内容如下:creator_trx_id=0,trx_ids= [10,20] , up_limit_id=10, low_limit_id=21。

由于id大于等于1的数据只有一个,且该数据的trx_id为8,小于up_limit_id,所以可以读取到。

在这之后id为10的事务新增了一行数据,增加了id为2的数据,且提交了。

MySQL MVVC 다중 버전 동시성 제어를 구현하는 방법

此时当前线程继续查找id>=1的数据,因为是可重复读,复用刚刚的Read View。

得到两行数据,但是因为id为2的数据trx_id为10,该值在Read View的trx_ids中存在,所以该记录对当前事务不可见,所以最后查询到的数据只有一条记录。

如果当前事务再插入id为2的数据就插不进去,所以说MVVC只解决了一半的幻读问题。

위 내용은 MySQL MVVC 다중 버전 동시성 제어를 구현하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:yisu.com
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
Transaction 10Transaction 20
BEGIN;
UPDATE Student SET name='李思' WHERE id =1;
UPDATE 학생 SET 이름='王五' WHERE id=1;
COMMIT; HERE id =1;
UPDATE 학생 SET 이름='Qianqi' WHERE id=1;
COMMIT;