이 기사는 MySQL의 트랜잭션을 이해하고 MVCC의 원리를 소개하는 데 도움이 되기를 바랍니다.
데이터베이스 트랜잭션은 일련의 데이터 작업을 의미합니다. 트랜잭션 내의 작업은 모두 성공하거나 모두 실패합니다. 실제로 아무것도 수행되지 않을 수도 있습니다. 모든 작업을 롤백하는 것은 아무것도 하지 않고 아무 것도 하지 않는 것과 비슷합니다.
MySQL에서는 트랜잭션 지원이 엔진 레이어에서 구현됩니다. MySQL은 여러 엔진을 지원하는 시스템이지만 모든 엔진이 트랜잭션을 지원하는 것은 아닙니다. 예를 들어, MySQL의 기본 MyISAM 엔진은 트랜잭션을 지원하지 않습니다. 이는 MyISAM이 InnoDB로 대체된 중요한 이유 중 하나입니다.
1.1 네 가지 주요 특징
1.2 격리 수준
SQL 트랜잭션의 네 가지 특성 중 원자성, 일관성, 내구성은 모두 비교적 이해하기 쉽습니다. 하지만 트랜잭션의 격리 수준은 실제로 어렵습니다. 오늘은 주로 MySQL 트랜잭션의 격리에 대해 이야기하겠습니다.
SQL 표준 트랜잭션 격리 수준은 낮음부터 높음까지 커밋되지 않은 읽기(커밋되지 않은 읽기), 커밋된 읽기(커밋된 읽기), 반복 가능한 읽기(반복 가능한 읽기) 및 직렬화 가능(직렬화 가능)입니다. 레벨이 높을수록 효율성은 낮아집니다.
1.3 동시성 문제 해결
SQL 트랜잭션 격리 수준은 동시성 문제를 최대한 해결하도록 설계되었습니다.
다음 표에 표시된 것처럼 다양한 SQL 트랜잭션 격리 수준은 다양한 동시성 문제를 해결할 수 있습니다. 직렬화 격리 수준만이 세 가지 문제를 모두 해결하고 나머지 세 가지 격리 수준에는 결함이 있습니다.
트랜잭션 격리 수준 | 더티 읽기 | 반복 불가능한 읽기 | 팬텀 읽기 |
---|---|---|---|
커밋되지 않은 읽기 | Possible | Possible | 가능 |
읽음 제출됨 | 불가능 | 가능 | 가능 |
반복읽기 | 불가능 | 불가능 | 가능 |
직렬화 | 불가능 | 불가능 | 불가능 |
PS: 비반복 읽기와 팬텀 읽기를 혼동하기 쉽습니다. 비반복 읽기는 수정에 중점을 두고, 팬텀 읽기는 추가 또는 삭제에 중점을 둡니다. 반복되지 않는 읽기 문제를 해결하려면 조건에 맞는 행만 잠그면 됩니다. 환상 읽기 문제를 해결하려면 테이블을 잠그면 됩니다
1.4 예를 들어보세요
이것입니다. 조금 이해하기 어려울 수도 있습니다. 예를 들어 보겠습니다. 테이블 구조와 테이블 데이터는 여전히 동일합니다
CREATE TABLE `student` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `age` int(11) NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 66 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
이제 동시에 두 가지 음식을 시작하려고 한다고 가정해 보겠습니다. 트랜잭션 A는 ID = 2로 학생의 나이를 쿼리하고 트랜잭션 B는 업데이트합니다. ID = 2 인 학생의 나이. 4가지 격리 수준에서 X1, X2, X3의 값은 무엇인가요?
그렇다면 왜 이런 결과가 나오는 걸까요? 트랜잭션 격리 수준은 어떻게 구현됩니까?
거래 격리 수준은 어떻게 구현되나요? 저는 Geek Time의 Ding Qi 선생님 수업에서 답을 찾았습니다.
사실 데이터베이스에 뷰가 생성되고, 액세스할 때 뷰의 논리적 결과가 우선합니다. "반복 읽기" 격리 수준에서 이 뷰는 트랜잭션이 시작될 때 생성되고 트랜잭션 전체에서 사용됩니다. "읽기 커밋" 격리 수준에서 이 뷰는 각 SQL 문의 시작 부분에 생성됩니다. 여기서 주목해야 할 점은 "커밋되지 않은 읽기" 격리 수준에서는 뷰 개념 없이 레코드의 최신 값이 직접 반환되는 반면, "직렬화" 격리 수준에서는 병렬을 피하기 위해 잠금이 직접 사용된다는 것입니다. 접속.
1.5 트랜잭션 격리 수준 설정
다양한 데이터베이스에 의해 설정되는 기본 트랜잭션 격리 수준도 매우 다릅니다. Oracle 데이터베이스의 기본 격리 수준은 읽기 커밋인 반면 MySQL은 반복 읽기입니다. 따라서 시스템이 Oracle에서 MySQL로 데이터베이스를 마이그레이션해야 하는 경우 예측할 수 없는 문제를 방지하기 위해 마이그레이션(읽기-커밋) 전과 일치하도록 레벨을 설정하십시오.
1.5.1 트랜잭션 격리 수준 보기
# 查看事务隔离级别 5.7.20 之前 SELECT @@transaction_isolation show variables like 'transaction_isolation'; # 5.7.20 以及之后 SELECT @@tx_isolation show variables like 'tx_isolation' +---------------+-----------------+ | Variable_name | Value | +---------------+-----------------+ | tx_isolation | REPEATABLE-READ | +---------------+-----------------+
1.5.2 격리 수준 설정
격리 수준 수정을 위한 문 형식은 다음과 같습니다. level]
범위는 선택 사항입니다: SESSION(세션), GLOBAL(전역); 격리 수준은 위에서 언급한 네 가지이며 대소문자를 구분하지 않습니다.
예: 전역 격리 수준을 커밋된 읽기로 설정
set global transaction isolation level read committed;
1.6 트랜잭션 시작
MySQL 트랜잭션은 다음과 같은 방법으로 시작할 수 있습니다.
# 更新学生名字 START TRANSACTION; update student set name = '张三' where id = 2; commit;
理解了隔离级别,那事务的隔离是怎么实现的呢?要想理解事务隔离,先得了解 MVCC 多版本的并发控制这个概念。而 MVCC 又依赖于 undo log 和 read view 实现。
2.1 什么是 MVCC?
百度上的解释是这样的:
MVCC,全称 Multi-Version Concurrency Control,即多版本并发控制。MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
MVCC 使得数据库读不会对数据加锁,普通的 SELECT 请求不会加锁,提高了数据库的并发处理能力;数据库写才会加锁。 借助 MVCC,数据库可以实现 READ COMMITTED,REPEATABLE READ 等隔离级别,用户可以查看当前数据的前一个或者前几个历史版本,保证了 ACID 中的 I 特性(隔离性)。
MVCC 只在 REPEATABLE READ 和 READ COMMITIED 两个隔离级别下工作。其他两个隔离级别都和 MVCC 不兼容 ,因为 READ UNCOMMITIED 总是读取最新的数据行,而不是符合当前事务版本的数据行。而 SERIALIZABLE 则会对所有读取的行都加锁。
2.1.1 InnDB 中的 MVCC
InnDB 中每个事务都有一个唯一的事务 ID,记为 transaction_id。它在事务开始时向 InnDB 申请,按照时间先后严格递增。
而每行数据其实都有多个版本,这就依赖 undo log 来实现了。每次事务更新数据就会生成一个新的数据版本,并把 transaction_id 记为 row trx_id。同时旧的数据版本会保留在 undo log 中,而且新的版本会记录旧版本的回滚指针,通过它直接拿到上一个版本。
所以,InnDB 中的 MVCC 其实是通过在每行记录后面保存两个隐藏的列来实现的。一列是事务 ID:trx_id;另一列是回滚指针:roll_pt。
2.2 undo log
回滚日志保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读。
根据操作的不同,undo log 分为两种: insert undo log 和 update undo log。
2.2.1 insert undo log
insert 操作产生的 undo log,因为 insert 操作记录没有历史版本只对当前事务本身可见,对于其他事务此记录不可见,所以 insert undo log 可以在事务提交后直接删除而不需要进行 purge 操作。
purge 的主要任务是将数据库中已经 mark del 的数据删除,另外也会批量回收 undo pages
所以,插入数据时。它的初始状态是这样的:
2.2.2 update undo log
UPDATE 和 DELETE 操作产生的 Undo log 都属于同一类型:update_undo。(update 可以视为 insert 新数据到原位置,delete 旧数据,undo log 暂时保留旧数据)。
事务提交时放到 history list 上,没有事务要用到这些回滚日志,即系统中没有比这个回滚日志更早的版本时,purge 线程将进行最后的删除操作。
한 트랜잭션이 현재 데이터를 수정합니다.
또 다른 트랜잭션이 데이터를 수정합니다.
데이터베이스에 동일한 레코드의 여러 버전이 있는데, 이는 다중 버전 동시성 제어 MVCC에서 언급된 것입니다. 위에 .
또한 실행 취소 로그를 사용하여 이전 버전 상태로 롤백할 수 있습니다. 예를 들어 V1으로 돌아가려면 두 번의 롤백만 순차적으로 수행하면 됩니다.
2.3 read-view
read view는 RC(Read Commit) 및 RR(Repeatable Read) 격리 수준 구현을 지원하기 위해 MVCC를 구현할 때 InnDB에서 사용하는 일관된 읽기 뷰입니다.
읽기 보기는 실제로 존재하지 않으며 단지 개념일 뿐이며 실행 취소 로그는 이를 구체화한 것입니다. 주로 버전과 로그 취소를 통해 계산됩니다. 그 기능은 거래가 볼 수 있는 데이터를 결정하는 것입니다.
각 거래 또는 명세서에는 고유한 일관성 보기가 있습니다. 일반적인 쿼리 문은 일관된 읽기입니다. 일관된 읽기는 행 trx_id 및 일관된 보기를 기반으로 데이터 버전의 가시성을 결정합니다.
2.3.1 데이터 버전의 가시성 규칙
읽기 뷰에는 주로 현재 시스템의 다른 활성 읽기 및 쓰기 트랜잭션이 포함됩니다. 구현 시 InnDB는 이 트랜잭션이 시작되는 순간 저장하기 위해 각 트랜잭션에 대한 배열을 구성합니다. 현재 활성(아직 제출되지 않은) 거래입니다. 앞서 언급했듯이 트랜잭션 ID는 시간이 지남에 따라 엄격하게 증가합니다.
시스템에 제출된 트랜잭션 ID의 최대 값은 배열의 저수위로 기록되고, 생성된 트랜잭션 ID + 1은 최고 수위로 기록됩니다. 레벨.
이 뷰 배열과 높은 수위는 현재 트랜잭션의 일관성 뷰(읽기 뷰)를 형성합니다.이 배열의 그림을 그리세요. 다음과 같습니다.
규칙은 다음과 같습니다. :
1 trx_id인 경우 회색 영역에서는 액세스한 버전의 trx_id가 배열의 저수위 id 값보다 작다는 것을 나타냅니다. 즉, 이 버전을 생성한 트랜잭션이 생성 전에 커밋되었음을 나타냅니다. 읽기 보기이므로 이 버전이 표시되고 현재 트랜잭션에서 액세스할 수 있습니다.이것은 다소 이해하기 어려울 수 있습니다. 3개의 트랜잭션이 동일한 데이터를 쿼리하고 업데이트한다고 가정합니다. 이해하기 쉽도록 그림을 그렸습니다.
원본 데이터는 여전히 아래 그림과 같습니다. , ID = 2인 Zhang San 정보 업데이트:
위 사진과 관련하여 질문을 드리고 싶습니다. RC(Read Committed) 및 RR(Repeatable Read) 격리 수준에서 각각 T4 및 T5 시점의 쿼리 수명 값은 무엇입니까? T4의 업데이트된 가치는 무엇인가요? 잠시 생각해보세요. 모두가 자신만의 답을 가지고 있다고 믿습니다. 답변은 기사 마지막 부분에 있습니다. 자신만의 질문을 가지고 계속해서 읽어보시기 바랍니다.
2.3.2 RR(반복 읽기)
RR 수준의 결과, 쿼리는 트랜잭션이 시작되기 전에 제출된 데이터만 인식하고, 트랜잭션이 시작되면 뷰가 빌드됩니다. 따라서 일관된 스냅샷 명령으로 트랜잭션 시작을 사용하면 뷰가 즉시 생성됩니다.
现在假设:
在这种隔离级别下,他们创建视图的时刻如下:
根据上图得,事务 A 的视图数组是[2,3];事务 B 的视图数组是 [2,3,4];事务 C 的视图数组是[2,3,4,5]。分析一波:
T4 时刻,B 读数据都是从当前版本读起,过程是这样的:
T5 时刻,A 读数据都是从当前版本读起,过程是这样的:
这样执行下来,虽然期间这一行数据被修改过,但是事务 A 不论在什么时候查询,看到这行数据的结果都是一致的,所以我们称之为一致性读。
其实视图是否可见主要看创建视图和提交的时机,总结下规律:
事务 B 的 update 语句,如果按照上图的一致性读,好像结果不大对?
如下图周明,B 的视图数组是先生成的,之后事务 C 才提交。那就应该看不见 C 修改的 age = 23 呀?最后 B 怎么得出 24 了?
没错,如果 B 在更新之前执行查询语句,那返回的结果肯定是 age = 22。问题是更新就不能在历史版本更新了呀,否则 C 的更新不就丢失了?
所以,更新有个规则:更新数据都是先读后写(读是更新语句执行,不是我们手动执行),读的就是当前版本的值,叫当前读;而我们普通的查询语句就叫快照读。
因此,在更新时,当前读读到的是 age = 23,更新之后就成 24 啦。
除了更新语句,查询语句如果加锁也是当前读。如果把事务 A 的查询语句 select age from t where id = 2 改一下,加上锁(lock in mode 或者 for update),也都可以得到当前版本 4 返回的 age = 24
下面就是加了锁的 select 语句:
select age from t where id = 2 lock in mode; select age from t where id = 2 for update;
假设事务 C 不马上提交,但是 age = 23 版本已生成。事务 B 的更新将会怎么走呢?
事务 C 还没提交,写锁还没释放,但是事务 B 的更新必须要当前读且必须加锁。所以事务 B 就阻塞了,必须等到事务 C 提交,释放锁才能继续当前的读。
2.3.3 RC(读提交)下的结果
在读提交隔离级别下,查询只承认在语句启动前就已经提交完成的数据;每一个语句执行之前都会重新算出一个新的视图。
注意:在上图的表格中用于启动事务的是 start transaction with consistent snapshot 命令,它会创建一个持续整个事务的视图。所以,在 RC 级别下,这命令其实不起作用。等效于普通的 start transaction(在执行 sql 语句之前才算是启动了事务)。所以,事务 B 的更新其实是在事务 C 之后的,它还没真正启动事务,而 C 已提交。
现在假设:
이 격리 수준에서 뷰를 생성하는 순간은 다음과 같습니다.
위 그림에 따르면 트랜잭션 A의 뷰 배열은 [2,3,4]입니다. 그러나 상위 워터마크는 6 이상입니다(생성된 트랜잭션 ID + 1). 트랜잭션 B의 뷰 배열은 [2,4]입니다. 분석의 물결:
T4 시간에 B는 현재 버전의 데이터를 읽습니다. 프로세스는 다음과 같습니다.
T5 시점에 A는 현재 버전에서 데이터를 읽습니다. 프로세스는 다음과 같습니다.
이 기사에서는 네 가지 주요 기능, 격리 수준, 동시성 문제 해결, 설정 방법, 격리 수준 확인, 트랜잭션 시작 방법 등과 같은 트랜잭션의 모든 측면을 자세히 설명합니다. 또한 두 가지 격리 수준인 RR과 RC가 어떻게 달성되는지에 대한 심층적인 이해도 얻었습니다. MVCC, 실행 취소 로그 및 읽기 보기가 함께 작동하여 MVCC를 구현하는 방법에 대한 자세한 설명을 포함합니다. 마지막으로 스냅샷 읽기, 현재 읽기 등에 대해 이야기했습니다. 업무에 관련된 모든 지식 포인트가 여기에 있다고 할 수 있습니다. 이 글을 읽어도 여전히 이해가 안 되신다면, 와서 저를 때려주세요!
【관련 추천: mysql 비디오 튜토리얼】
위 내용은 MySQL의 트랜잭션 및 MVCC 원리를 자세히 설명하는 기사의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!