2020-07-16—MySQL隔离级别

前几天面试,被问到了数据库MySQL隔离级别,我自己一时间没想起来,说岔了频道,说成了事务的传播属性,现在来总结一下。

事务请看博客(https://blog.lovewinter.top/2020/07/09/spring-aop-yu-shi-wu/)

或者简书地址:https://www.jianshu.com/p/00067fa029ef

这次主动总结一下隔离级别方面的知识。

MySQL隔离级别的总结

事务的4个特征

事务应该具有 4 个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为 ACID 特

性。

  • 原子性(atomicity)

  • 一致性(consistency)

  • 隔离性(isolation)

  • 持久性(durability)

Atomic(原子性):

事务中包括的操作被看做一个逻辑单元。这个逻辑单元中的操作要
么所有成功。要么所有失败。

Consistency(一致性):

仅仅有合法的数据能够被写入数据库,否则事务应该将其回滚到最初
状态。

Durability(持久性):

事务结束后。事务处理的结果必须可以得到固化。

Isolation(隔离性):

事务同意多个用户对同一个数据进行并发訪问,而不破坏数据的正
确性和完整性。同一时候。并行事务的改动必须与其它并行事务的改动
相互独立。


那么隔离性中就牵涉到几种隔离级别:

一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事

务是隔离的,并发执行的各个事务之间不能互相干扰。

(对数据库的并行执行,应该像串行执行一样)

  • 未提交读(READ UNCOMMITED)脏读

  • 已提交读 (READ COMMITED)不可重复读

  • 可重复读(REPEATABLE READ)

  • 可串行化(SERIALIZABLE)

mysql 默认的事务隔离级别为 repeatable-read

show variables like ‘%tx_isolation%’;

数据库事务的隔离级别有4个。由低到高依次为Read uncommitted、Read committed、Repeatable read、Serializable。这四个级别能够逐个解决脏读、不可反复读、幻读这几类问题。MySql设置的隔离级别默觉得Repeatable Read。可反复读级别。

隔离级别能够配置。

√: 可能出现 ×: 不会出现

脏读 不可反复读 幻读
Read uncommitted
Read committed ×
Repeatable read × ×
Serializable × × ×

注意:我们讨论隔离级别的场景,主要是在多个事务并发的情况下。因此,接下来的解说都环绕事务并发。

Read uncommitted 读未提交

READ UNCOMMITTED是限制性最弱的隔离级别。由于该级别忽略其它事务放置的锁。使用READ UNCOMMITTED级别运行的事务,能够读取尚未由其它事务提交的改动后的数据值,这些行为称为“脏”读。我们所说的脏读,两个并发的事务,“事务A:领导给singo发工资”、“事务B:singo查询工资账户”,事务B读取了事务A尚未提交的数据。比方,事务1改动一行,事务2在事务1提交之前读取了这一行。

假设事务1回滚,事务2就读取了一行没有提交的数据。这种数据我们觉得是不存在的。

Read committed 读提交

该级别通过指定语句不能读取其它事务已改动可是尚未提交的数据值。禁止运行脏读。在当前事务中的各个语句运行之间,其它事务仍能够改动、插入或删除数据(重点是事务B仍然具有改动插入和删除的权限,所以产生不可反复读)。从而产生无法反复的读操作。或“影子”数据。比方,事务1读取了一行。事务2改动或者删除这一行而且提交。假设事务1想再一次读取这一行,它将获得改动后的数据或者发现这一样已经被删除。因此事务的第二次读取结果与第一次读取结果不同,因此也叫不可反复读。

大多数数据库的默认级别就是Read committed。比方Sql Server , Oracle。怎样解决不可反复读这一问题。请看下一个隔离级别。

不可重复读的重点是修改:

Repeatable read 反复读

REPEATABLE READ是比READ COMMITTED限制性更强的隔离级别。

该级别包含READ COMMITTED,而且另外指定了在当前事务提交之前。其它不论什么事务均不能够改动或删除当前事务已读取的数据(可以插入)。并发性低于 READ COMMITTED。由于已读数据的共享锁在整个事务期间持有,而不是在每一个语句结束时释放。

这个隔离级别仅仅是说,不可以改动和删除,可是并没有强制不能插入新的满足条件查询的数据行。

此可以得出结论:REPEATABLE READ隔离级别保证了在同样的查询条件下,同一个事务中的两个查询。第二次读取的内容肯定包换第一次读到的内容。注:Mysql的默认隔离级别就是Repeatable read。

幻读的重点在于新增或者删除。

反复读与幻读

反复读是为了保证在一个事务中,相同查询条件下读取的数据值不发生改变,可是不能保证下次相同条件查询。结果记录数不会添加。

幻读就是为了解决问题而存在的,他将这个查询范围都加锁了。所以就不能再往这个范围内插入数据。这就是SERIALIZABLE 隔离级别做的事情。

Serializable 序列化

SERIALIZABLE 是限制性最强的隔离级别,由于该级别锁定整个范围的键。并一直持有锁,直到事务完毕。该级别包含REPEATABLE READ。并添加了在事务完毕之前,其它事务不能向事务已读取的范围插入新行的限制。比方,事务1读取了一系列满足搜索条件的行。事务2在运行SQL statement产生一行或者多行满足事务1搜索条件的行时会冲突。则事务2回滚。这时事务1再次读取了一系列满足同样搜索条件的行。第二次读取的结果和第一次读取的结果同样。

**Serializable
**这个级别非常easy。读加共享锁。写加排他锁,读写相互排斥。使用的悲观锁的理论,实现简单,数据更加安全。可是并发能力非常差。假设你的业务并发的特别少或者没有并发,同一时候又要求数据及时可靠的话,能够使用这样的模式。

这里要吐槽一句,不要看到select就说不会加锁了。在Serializable这个级别,还是会加锁的。

下面是测试案例:

--事务并发问题
--脏读:
事务 A 读取了事务 B 更新的数据,然后 B 回滚操作,那么 A 读取到的数据是脏数据
--不可重复读:
事务 A 多次读取同一数据,事务 B 在事务 A 多次读取的过程中,对数 据作了更新并提交,导致事务 A 多次读取同一数据时,结果 不一致。
--幻读:
系统管理员 A 将数据库中所有学生的成绩从具体分数改为 ABCDE 等级,但是系 统管理员 B 就在这个时候插入了一条具体分数的记录,当系统管理员 A 改结束后发现 还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。 不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不 可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。
/**
* 未提交读(READ UNCOMMITED)
* 脏读
*/
show variables like '%tx_isolation%';
set SESSION TRANSACTION ISOLATION LEVEL read UNCOMMITTED;
-- 一个 session 中 :
start TRANSACTION update account set balance = balance -50 where id = 1
--另外一个 session 中查询
select * from account
--回到第一个 session 中 回滚事务 ROLLBACK
--在第二个 session 中
select * from account
--在另外一个 session 中读取到了为提交的数据,这部分的数据为脏数据

/**
* 已提交读 (READ COMMITED)
* 不可重复读
*/
show variables like '%tx_isolation%';
set SESSION TRANSACTION ISOLATION LEVEL read committed;
-- 一个 session 中
start TRANSACTION update account set balance = balance -50 where id = 1
-- 另外一个 session 中查询 (数据并没改变)
select * from account
-- 回到第一个 session 中 回滚事务
commit
-- 在第二个 session 中
select * from account (数据已经改变)

/**
* 可重复读(REPEATABLE READ)
*/
show variables like '%tx_isolation%';
set SESSION TRANSACTION ISOLATION LEVEL repeatable read;
-- 一个 session 中
start TRANSACTION
update account set balance = balance -50 where id = 1
-- 另外一个 session 中查询 (数据并没改变)
select * from account
-- 回到第一个 session 中
回滚事务 commit
-- 在第二个 session 中
select * from account (数据并未改变)

/**
* 可串行化(SERIALIZABLE)
* ------读加共享锁。写加排他锁,读写相互排斥。
*/
show variables like '%tx_isolation%';
set SESSION TRANSACTION ISOLATION LEVEL serializable;
--1.开启一个事务
begin
select * from account 发现 3 条记录
--2.开启另外一个事务
begin
select * from account 发现 3 条记录,也是 3 条记录
insert into account VALUES(4,'deer',500) 发现根本就不让插入
--3. 回到第一个事务 commit,然后在第二个事务中就可以insert了


/**
* 间隙锁(gap 锁)
*/
其实在 mysql 中,可重复读已经解决了幻读问题,借助的就是间隙锁
--实验 1:
select @@tx_isolation;
create table t_lock_1 (a int primary key);
insert into t_lock_1 values(10),(11),(13),(20),(40);
begin
select * from t_lock_1 where a <= 13 for update;
--在另外一个会话中
insert into t_lock_1 values(21) 成功
insert into t_lock_1 values(19) 阻塞
在 rr 隔离级别中者个会扫描到当前值(13)的下一个值(20),并把这些数据全部加锁


实验:2
create table t_lock_2 (a int primary key,b int, key (b));
insert into t_lock_2 values(1,1),(3,1),(5,3),(8,6),(10,8);
--会话 1 :
BEGIN
select * from t_lock_2 where b=3 for update;
1 3 5 8 10
1 1 3 6 8
--会话 2 :
select * from t_lock_2 where a = 5 lock in share mode; -- 不可执行,因为 a=5 上有一把记录锁
insert into t_lock_2 values(4, 2); -- 不可以执行,因为 b=2 在(1, 3]内
insert into t_lock_2 values(6, 5); -- 不可以执行,因为 b=5 在(3, 6)内
insert into t_lock_2 values(2, 0); -- 可以执行,(2, 0)均不在锁住的范围内
insert into t_lock_2 values(6, 7); -- 可以执行,(6, 7)均不在锁住的范围内
insert into t_lock_2 values(9, 6); -- 可以执行
insert into t_lock_2 values(7, 6); -- 不可以执行
二级索引锁住的范围是 (1, 3],(36) 主键索引只锁住了 a=5 的这条记录 [5]
发布于

2020-07-16

更新于

2022-03-25

许可协议

评论

:D 一言句子获取中...

加载中,最新评论有1分钟缓存...