MySQL事务隔离级别及MVCC详解

MySQL的事务隔离级别及特性如下:

SERIALIZABLE(串行化):无问题,性能差;

REPEATABLE-READ(可重复读):幻读,默认隔离级别;

READ-COMMITTED(已提交读):不可重复读&幻读;

READ-UNCOMMITTED(未提交读):脏读&不可重复读&幻读。

串行化就是事务按顺序执行,没有并行,自然也就没有问题。但是性能就会比较差;

可重复读是MySQL默认的隔离级别,就是一个事务中多次读取数据,读到的数据都是一致的,不受其他事务修改的影响;

已提交读就是可以读到其他事务已经提交的数据,所以就没有了可重复读的特性;

未提交读能读到其他事务已执行但是未提交的数据,这就会产生脏读。

数据库事务隔离级别的查看和修改

sql 复制代码
-- 查看(5.7)
select @@global.tx_isolation;
select @@tx_isolation;
-- 查看(8)
select @@global.transaction_isolation;
select @@transaction_isolation;
-- 修改
set session transaction isolation level serialiable/repeatable read/......

可重复读出现幻读的情况

A事务:

sql 复制代码
start transaction;
select * from table where id = 2; //此时表中无id为2的数据

B事务:

sql 复制代码
start transaction;
insert into table(id, name) values(2, 'Wang');
commit;

A事务:

sql 复制代码
select * from table where id = 2; // 此时虽然表中已经有了,但是由于可重复读的特性,同一事务读到的数据是一致的,所以还是读不到
insert into table(id, name) values(2, 'Wang'); //报错
commit;

已提交读出现不可重复读的情况

A事务:

sql 复制代码
start transaction;
select * from table where id = 2; // 此时可以看到2的name是'Wang'

B事务:

sql 复制代码
start transaction;
update table set name = 'Yan' where id = 2;
commit;

A事务:

sql 复制代码
select * from table where id = 2; // 此时2的name变成了'Yan',这样就出现了不可重复度的问题
commit;

已提交读出现幻读的情况

A事务:

sql 复制代码
start transaction;
select * from table where id = 3; // 此时没有id为3的数据,

B事务:

sql 复制代码
start transaction;
insert into table(id, name) values(3, 'Li'); // 只插入不提交

A事务:

sql 复制代码
select * from table where id = 3; // 此时还是读不到3的数据
insert into table(id, name) values(3, 'Li'); // 此时插入数据就会有问题,因为B事务还没提交,但是已经执行了,A事务只能等待,看B事务是回滚还是提交

未提交读出现不可重复读和脏读的情况

A事务:

sql 复制代码
start transaction;
select * from table where id = 2; // 此时读到2的name为'Yan'

B事务:

sql 复制代码
start transaction;
update table set name = 'Sun' where id = 2; // 只执行不提交

A事务:

sql 复制代码
select * from table where id = 2; // 此时读到2的name为'Sun',这就已经出现了不可重复读。假设B事务现在回滚了,那么这个数据就是错的,就出现了脏读。

为提交读出现幻读的情况

A事务:

sql 复制代码
start transaction;
select * from table; // 查出全表数据为[1-'Wu'], [2-'Sun'], [3-'Li']

B事务:

sql 复制代码
delete from table where id = 3; // 删除了但是没提交

A事务:

sql 复制代码
select * from table; // 查出全表数据为[1-'Wu'], [2-'Sun'],查到3被删除了。此时B事务回滚,数据3又出来了,一会有一会没有,就出现了幻读。

MVCC

MVCC即多版本并发控制(Multi-Version Concurrent Control)。

使用可重复读和已提交读这两种隔离级别时,SELECT操作版本链的过程。

MySQL数据行中存在隐藏的3个列:row_id、trx_id、roll_pointer

row_id如果表中有主键,或者有非NULL唯一键,就不需要row_id。没有则会自动生成6个字节的row_id。

trx_id为事务id,每次开启事务都会生成,记录在数据行中。

roll_pointer则是会指向undo_log中的上一次更新的数据。更新多次就会形成版本链。

SELECT操作会生成ReadView,已提交读同一个事务中对相同数据的查询每次SELECT都会生成一个,而可重复读一个事务多个SELECT只会生成一个ReadView。

ReadView中有m_ids、min_trx_id、max_trx_id、creator_trx_id。

m_ids:生成ReadView时所有未提交的事务id。

min_trx_id:m_ids的最小值。

max_trx_id:生成ReadView时,系统应该分配给下一个事务的事务id。

creator_trx_id:生成ReadView的事务的事务id,如果只有SELECT则为0。

当SELECT生成ReadView后,从当前B+树中的数据开始查,顺着版本链从上往下查,知道找到ReadView生成前提交的最新记录,返回即可。有以下几种情况:

  1. 被访问数据的事务id小于min_trx_id,返回。
  2. 被访问数据的事务id大于等于max_trx_id,不返回。
  3. 被访问数据的事务id在m_ids中,不返回。
  4. 被访问数据的事务id在min_trx_id和max_trx_id之间,且不在m_ids中,返回。

已提交读举例

有数据[1-Wang], [2-Yan], [3-Li],插入事务id均为80。

A事务id为100更新1 = Sun,不提交。

B事务id为200更新其他表,不提交。

C事务读id为1的数据,生成ReadView,m_ids=[100, 200],min_trx_id=100, max_trx_id=201,creator_trx_id=0。

这时就该从版本链中开始寻找数据:

  1. id为1的数据目前的trx_id为100,100在m_ids中,说明那个事务在当前事务开始的时候还没执行完,不应该读出来,继续向下寻找。
  2. 此时roll_pointer指向的上次undo_log记录trx_id为80,80小于min_trx_id,说明这个事务在生成ReadView之前已经提交了,那就会读到'Wang'。

A事务提交。

B事务更新1 = Liu,不提交。

C事务读id为1的数据,再次生成ReadView,m_ids=[200],min_trx_id=200,max_trx_id=201,creator_trx_id=0。

从版本链中开始寻找数据:

  1. id为1的数据目前的trx_id为200,200在m_ids中,继续向下寻找。
  2. 下面roll_pointer指向的上一条记录trx_id为100,小于min_trx_id,所以返回了A事务修改的'Sun'。
可重复读举例

有数据[1-Wang], [2-Yan], [3-Li],插入事务id均为80。

A事务id为100更新1 = Sun,不提交。

B事务id为200更新其他表,不提交。

C事务读id为1的数据,生成ReadView,m_ids=[100, 200],min_trx_id=100, max_trx_id=201,creator_trx_id=0。

这时就该从版本链中开始寻找数据:

  1. id为1的数据目前的trx_id为100,100在m_ids中,说明那个事务在当前事务开始的时候还没执行完,不应该读出来,继续向下寻找。
  2. 此时roll_pointer指向的上次undo_log记录trx_id为80,80小于min_trx_id,说明这个事务在生成ReadView之前已经提交了,那就会读到'Wang'。

A事务提交。

B事务更新1 = Liu,不提交。

C事务读id为1的数据,由于是可重复读隔离级别,所以不再生成新的ReadView,还是用之前的,m_ids=[100, 200],min_trx_id=100, max_trx_id=201,creator_trx_id=0。

从版本链中开始寻找数据:

  1. id为1的数据目前的trx_id为200,200在m_ids中,继续向下寻找。
  2. 下面roll_pointer指向的上一条记录trx_id为100,还是在m_ids中,继续向下寻找。
  3. 再一条记录trx_id为80,小于min_trx_id,所以返回了最初的'Wang'。
相关推荐
qq_433099401 小时前
Ubuntu20.04从零安装IsaacSim/IsaacLab
数据库
Dlwyz1 小时前
redis-击穿、穿透、雪崩
数据库·redis·缓存
工业甲酰苯胺3 小时前
Redis性能优化的18招
数据库·redis·性能优化
没书读了4 小时前
ssm框架-spring-spring声明式事务
java·数据库·spring
i道i4 小时前
MySQL win安装 和 pymysql使用示例
数据库·mysql
小怪兽ysl4 小时前
【PostgreSQL使用pg_filedump工具解析数据文件以恢复数据】
数据库·postgresql
wqq_9922502775 小时前
springboot基于微信小程序的食堂预约点餐系统
数据库·微信小程序·小程序
爱上口袋的天空5 小时前
09 - Clickhouse的SQL操作
数据库·sql·clickhouse
Oak Zhang6 小时前
sharding-jdbc自定义分片算法,表对应关系存储在mysql中,缓存到redis或者本地
redis·mysql·缓存
聂 可 以6 小时前
Windows环境安装MongoDB
数据库·mongodb