天空闲话 发表于 2024-11-18 06:16:08

【MySQL 保姆级讲授】事务的隔离级别(具体)--下(14)

1. 如何明白事务的隔离性

MySQL服务可能会同时被多个客户端进程(线程)访问,访问的方式以事务方式举行。
一个事务可能由多条SQL构成,也就意味着,任何一个事务,都有执行前,执行中,执行后的阶段。而所谓的原子性,其实就是让用户层,要么看到执行前,要么看到执行后。执行中出现问题,可以随时回滚。所以单个事务,对用户体现出来的特性,就是原子性。
但,毕竟所有事务都要有个执行过程,那么在多个事务各自执行多个SQL的时候,就还是有可能会出现相互影响的情况。比如:多个事务同时访问同一张表,甚至同一行数据。
比如,给同学1的任务是一次性把水池装满,给同学2的任务是一次性把水池的水放干:同学1在装水池的过程中而同学2放水池的水,结果他们俩个相互影响都完成不了任务。如果他们两个的工作隔离起来,先让同学1装水,水装满后同学2在举行放水,这样他们两个都可以完成任务。
数据库中,为了保证事务执行过程中尽量不受干扰,就有了一个重要特征:隔离性
数据库中,允许事务受不同水平的干扰,就有了一种重要特征:隔离级别
2. 事务隔离级别的分类


[*] 读未提交(Read Uncommitted)
界说:在读未提交隔离级别下,一个事务可以读取另一个事务尚未提交的数据。这是最低的隔离级别,允许脏读(Dirty Reads)。

[*]优点:提供最高的并发性能,因为事务之间险些没有壅闭。
[*]缺点:可能会出现脏读,即读取到其他事务未提交的数据。可能会出现不可重复读(Non-repeatable Reads)和幻读(Phantom Reads)。

[*] 读已提交(Read Committed)
界说:在读已提交隔离级别下,一个事务只能读取到另一个事务已经提交的数据。事务在每次读取数据时都会看到最新的已提交数据。

[*]优点:避免了脏读。提供了较好的并发性能。
[*]缺点:可能会出现不可重复读,即在同一事务中多次读取同一数据时,可能会得到不同的结果。可能会出现幻读。

[*] 可重复读(Repeatable Read)
界说:在可重复读隔离级别下,一个事务在执行期间多次读取同一数据时,结果始终相同,即使其他事务对这些数据举行了修改并提交。这是 MySQL 的默认隔离级别。

[*]优点:避免了脏读和不可重复读。提供了较高的数据划一性。
[*]缺点:可能会出现幻读,即在同一事务中多次执行相同的查询,可能会出现新的记录。

[*] 序列化(Serializable)
界说:在序列化隔离级别下,事务被完全隔离,每个事务都按次序执行,犹如在单线程环境中一样。这是最高的隔离级别,确保了数据的划一性,但并发性能较差。

[*]优点:避免了脏读、不可重复读和幻读。提供了最高的数据划一性。
[*]缺点:并发性能较低,因为事务之间会有更多的壅闭和等候。

3. 查看和设置事务隔离级别

3.1 全局和会话隔离级别


[*] 全局隔离级别(Global Isolation Level)

[*]全局隔离级别为所有新会话的默认隔离级别。
[*]一旦设置,所有新的会话将使用这个隔离级别,除非在会话中明确更改。
[*]对已经存在的会话没有影响。

[*] 会话隔离级别(Session Isolation Level)

[*]会话隔离级别为当前会话的隔离级别。
[*]默认的使用是全局隔离级别。
[*]修改后仅对当前会话有效,不影响其他会话。

3.2 查看和设置隔离级别

全局隔离级别的设置:
SET GLOBAL TRANSACTION ISOLATION LEVEL isolation_level;
查询全局的隔离级别:
SELECT @@GLOBAL.TX_ISOLATION;
会话隔离级别的设置:
SET SESSION TRANSACTION ISOLATION LEVEL isolation_level;
查询会话的隔离级别:
SELECT @@SESSION.TX_ISOLATION;
SELECT @@TX_ISOLATION;
示例:
查询全局隔离级别和会话隔离级别:
# 查看全局隔离级别
select @@global.tx_isolation;

# 查看会话
select @@session.tx_isolation;
https://i-blog.csdnimg.cn/direct/417e253f0cc84845b8b7e88d65f3c2aa.png
显然,全局隔离级别和会话隔离级别是一样的,因为当开启一个新的客户端时,会话隔离级别默认是全局的隔离级别。
修改会话的隔离级别为 read uncommitted 读未提交,再次查询会话隔离级别
set session transaction isolation level read uncommitted;
https://i-blog.csdnimg.cn/direct/23c9100349824750a8076b7d30727fb3.png
显然,会话的隔离级别已经变为读未提交。
修改全局的隔离级别为 read committed 读提交,然后查询全局和会话隔离级别
# 设置全局隔离级别
set global transaction isolation level read committed;

# 查询隔离级别
select @@global.tx_isolation;
select @@session.tx_isolation;
https://i-blog.csdnimg.cn/direct/4e0400187da44e5396c73210bafdb602.png
为什么设置全局隔离级别后,全局隔离级别和会话隔离级别不一样呢?
答:会话隔离级别默认的是上次修改的(创建会话时默认的是全局隔离级别),修改全局隔离级别不能直接修改当前会话的隔离级别。
退出MySQL,再次进入MySQL客户端,查看隔离级别
# 退出
quit

# 查询隔离级别
select @@global.tx_isolation;
select @@session.tx_isolation;
https://i-blog.csdnimg.cn/direct/777bef85b97a4d54bd81f1c17e05f78a.png
这时两者的隔离级别全是读提交。
4. 事务隔离级别的演示

4.1 读未提交(Read Uncommitted)

字面的意思来明白,当一个客户端在事务中操纵时并未提交事务,另一个客户端能读到该客户端操纵的数据。
界说:在读未提交隔离级别下,一个事务可以读取另一个事务尚未提交的数据。这是最低的隔离级别,允许脏读(Dirty Reads)。
优点:提供最高的并发性能,因为事务之间险些没有壅闭。
缺点:可能会出现脏读,即读取到其他事务未提交的数据。可能会出现不可重复读(Non-repeatable Reads)和幻读(Phantom Reads)。
险些没有加锁,虽然效率高,但是问题太多,严重不建议采用
举例:
创建两个会话。左侧为客户端1,右侧为客户端2。
设置读未提交隔离级别:
set session transaction isolation level read uncommitted;
https://i-blog.csdnimg.cn/direct/5e5a832c116f418a88a8ad71a01fe54d.png
在客户端1插入数据,客户端2查询数据。
        # 客户端1
        insert into students values(1, '李明', 18);
        # 创建保存点
        savepoint p1;
       
        # 客户端2
        select * from students;
https://i-blog.csdnimg.cn/direct/8e02ca89fedb4d1a9e6dff2f2b2eab1a.png
显然,在客户端1执行下令后未使用commit提交,在客户端2就可以直接查询出操纵结果。
一个事务在执行中,读到另一个执行中事务的更新(或其他操纵)但是未commit的数据,这种征象叫做脏读(dirty read)
在客户端1模拟崩溃;然后在客户端2查询数据
        # 客户端1
        `CRRL + D`
       
        # 客户端2
        select * from students;
https://i-blog.csdnimg.cn/direct/d66cda19961c4717abd00f6b3a3efdf8.png
很容易看出,当客户端1没有提交时系统崩溃后就会回滚到事务的开始,事务中所有的操纵都会被取消。
4.2 读已提交(Read Committed)

字面的意思来明白,当一个客户端在事务中操纵时并提交事务后,另一个客户端才能读到该客户端操纵的数据。
界说:在读已提交隔离级别下,一个事务只能读取到另一个事务已经提交的数据。事务在每次读取数据时都会看到最新的已提交数据。
优点:避免了脏读。提供了较好的并发性能。
缺点:可能会出现不可重复读,即在同一事务中多次读取同一数据时,可能会得到不同的结果。可能会出现幻读。
举例:
创建两个会话。左侧为客户端1,右侧为客户端2。
设置读已提交隔离级别:
set session transaction isolation level read committed;
https://i-blog.csdnimg.cn/direct/fab4a8dd74e4411da9198a2a8cf0b9d7.png
在客户端1插入数据,客户端2查询数据。
        # 客户端1
        insert into students values(1, '李明', 18);
       
        # 客户端2
        select * from students;
https://i-blog.csdnimg.cn/direct/1b5d3c9e12b34375a883dcb1de22bcbf.png
可以发现,客户端1插入数据后在客户端2并不能查询到插入的数据。
这是为什么呢?
答:读已提交隔离级别,一个事务只能读取到另一个事务已经提交的数据,在事务未提交之前其他的客户端时不能读取到的。
在客户端1插入数据并提交,客户端2查询数据。
        # 客户端1
        insert into students values(2, '诸葛亮', 20);
        commit;
       
        # 客户端2
        select * from students;
https://i-blog.csdnimg.cn/direct/a2ffa36d67b34dd1baeb2a30e92bc9d7.png
显然,当客户端1提交事务后客户端2就能读到数据了。
4.3 可重复读(Repeatable Read)

4.3.1 为什么要有可重复读?

读已提交不是已经够完善了吗,为什么还要有可重复读呢?
答:读已提交只是看上去很方便,但是在实际的工作中会有很大的问题,主要的问题就在于在事务中能读到其他已经提交的事务。
比如,学校要给考试结果好的门生发奖品,工作人员在筛选结果的同时,另一工作人员发现第9名同学的结果多算分了,正在减分。如图:
https://i-blog.csdnimg.cn/direct/30bb86aa88a34b6ba587cb2b25aeaea3.png
通过上图可知,小明的名字筛选出两次,那么生成的表中会有两个结果不一样的名字。这肯定是不符合的。
4.3.2 可重复读

界说:在可重复读隔离级别下,一个事务在执行期间多次读取同一数据时,结果始终相同,即使其他事务对这些数据举行了修改并提交。这是 MySQL 的默认隔离级别。
优点:避免了脏读和不可重复读。提供了较高的数据划一性。
缺点:可能会出现幻读,即在同一事务中多次执行相同的查询,可能会出现新的记录。
举例:
创建两个会话。左侧为客户端1,右侧为客户端2。
设置可重复读隔离级别:
set session transaction isolation level repeatable read;
https://i-blog.csdnimg.cn/direct/53bfdc48d77049488a21c49807e8a226.png
在客户端1插入数据,客户端2查询数据。
        # 客户端1
        insert into students values(3, '李白', 20);
       
        # 客户端2
        select * from students;
https://i-blog.csdnimg.cn/direct/6b0a33a4e10f40c6b9735e8f62889ec3.png
可以发现,客户端1插入数据后在客户端2并不能查询到插入的数据。
在客户端1插入数据后提交事务,客户端2查询数据。
        # 客户端1
        insert into students values(4, '嫦娥, 18);
        commit;
       
        # 客户端2
        select * from students;
https://i-blog.csdnimg.cn/direct/c99b0e3b83634823975043bf7aa2dc37.png
客户端1把事务提交后客户端2为什么还是不能查询到数据呢?
答:在可重复读隔离级别下,一个事务在执行期间多次读取同一数据时,结果始终相同,即使其他事务对这些数据举行了修改并提交。只用把自己的事务提交后,才能查询到其他已提交事务更改后的数据。
提交客户端2的事务后再举行查询
# 客户端2
#提交事务
commit;

# 查询
select * from students;
https://i-blog.csdnimg.cn/direct/3bd5c683efb246feb1f32491a2877fc4.png
显然,当客户端2提交事务后就可以查询到客户端已插入的数据。
4.3.3 说明

多次查看,发现终端A在对应事务中insert的数据,在终端B的事务周期中,也没有什么影响,也符合可重复的特点。
但是,一般的数据库在可重复读情况的时候,无法屏蔽其他事务insert的数据。
为什么?因为隔离性实现是对数据加锁完成的,而insert待插入的数据因为并不存在,那么一般加锁无法屏蔽这类问题,会造成虽然大部门内容是可重复读的,但是insert的数据在可重复读情况被读取出来,导致多次查找时,会多查找出来新的记录,就犹如产生了幻觉。
这种征象,叫做幻读(phantom read)。很明显,MySQL在RR级别的时候,是解决了幻读问题的
(解决的方式是用Next-Key锁(GAP+行锁)解决的。这块比力难,有爱好同学相识一下)。
4.4 序列化(Serializable)

序列化也叫做串行化。
界说:在序列化隔离级别下,事务被完全隔离,每个事务都按次序执行,犹如在单线程环境中一样。这是最高的隔离级别,确保了数据的划一性,但并发性能较差。
优点:避免了脏读、不可重复读和幻读。提供了最高的数据划一性。
缺点:并发性能较低,因为事务之间会有更多的壅闭和等候。
举例:
创建两个会话。左侧为客户端1,右侧为客户端2。

[*] 场景1:两个客户端都查询数据,客户端2提交事务,然后客户端1提交事务。
设置序列化隔离级别,开启事务
set session transaction isolation level serializable;


https://i-blog.csdnimg.cn/direct/499e6bd01f3140d8a695018ba47b67a0.png
客户端1和客户端2都对表举行查询(此时两个事务都对表举行了加锁)
# 客户端1
select * from students;
       
# 客户端2
select * from students;
https://i-blog.csdnimg.cn/direct/ebcba5aef7e4480093e880cb6c979493.png
客户端1举行插入表操纵时发生锁等候
https://i-blog.csdnimg.cn/direct/62f5862a537549c6a07bfbb1fcc7c73e.png
客户端1输入插入下令按回车键后,光标在下令的下面,说明该下令正在等候执行,这是为什么呢?
答:在序列化隔离级别下,产生锁是为了确保数据的划一性;事务中的下令只要对表举行查询,就会锁住表,但是其他事务中的下令对此表也可以举行锁住;表只有被一个事务锁住时才能举行修改操纵。
此时的表被两个事务锁住了,客户端1想举行修改操纵只有先让客户端2把事务提交(解开对表的锁)。
客户端2对表解锁:
# 客户端2
commit;
https://i-blog.csdnimg.cn/direct/d79f44a86de540549e59cc667ae7dfdb.png
[*] 场景2:两个客户端都查询数据,然后都插入数据
设置序列化隔离级别,开启事务
set session transaction isolation level serializable;


https://i-blog.csdnimg.cn/direct/499e6bd01f3140d8a695018ba47b67a0.png
客户端1和客户端2都对表举行查询(此时两个事务都对表举行了加锁)
# 客户端1
select * from students;
       
# 客户端2
select * from students;
客户端1对表举行插入,客户端2对表举行插入
# 客户端1
insert into students values(6,'6',6);

# 客户端2
insert into students values(7,'7',7);
https://i-blog.csdnimg.cn/direct/9c99da740fcb4df481082ea7b0ce4a82.png
当客户端2对表举行插入的时候发生死锁,什么是死锁呢?
答:两个事务试图同时访问同一个资源(比如插入同一条记录,就会发生死锁。MySQL会检测到这种情况,并返回错误信息,提示用户重启其中一个事务。
当客户端2发生死锁的时候就会被强制重启事务,此时读表的锁就只有客户端2了,客户端1对表的操纵就可以举行了。
[*] 场景3:两个客户端都查询数据,客户端1插入数据(等候插入数据)
设置序列化隔离级别,开启事务
set session transaction isolation level serializable;


https://i-blog.csdnimg.cn/direct/499e6bd01f3140d8a695018ba47b67a0.png
客户端1和客户端2都对表举行查询(此时两个事务都对表举行了加锁)
# 客户端1
select * from students;
       
# 客户端2
select * from students;
客户端1对表举行插入,客户端2不举行任何操纵
# 客户端1
insert into students values(6,'6',6);

# 客户端2

https://i-blog.csdnimg.cn/direct/d77b709b83f342e8982fddc44895c5e1.png
执行步调3后不在举行任何的操纵,过几分钟后步调4会自动弹出,修改表的操纵尝试获取锁时超过了等候时间限制,导致事务无法继续执行。
5. 总结



[*]其中隔离级别越严酷,安全性越高,但数据库的并发性能也就越低,每每必要在两者之间找一个平
衡点。
[*]不可重复读的重点是修改和删除:同样的条件, 你读取过的数据,再次读取出来发现值不一样了
[*]幻读的重点在于新增:同样的条件, 第1次和第2次读出来的记录数不一样
[*]说明:mysql 默认的隔离级别是可重复读,一般情况下不要修改
[*]上面的例子可以看出,事务也有是非事务这样的概念。事务间相互影响,指的是事务在并行执行的时候,即都没有commit的时候,影响会比力大。
隔离级别脏读不可重复读幻读加锁读读未提交(read uncommit)YESYESYESNO读已提交(read commit)NOYESYESNO可重复读(repeatable)NONONONO序列化(可串行化)(serializable)NONONOYES 划一性(Consistency) :


[*]事务执行的结果,必须使数据库从一个划一性状态,变到另一个划一性状态。当数据库只包罗事务
成功提交的结果时,数据库处于划一性状态。如果系统运行发生停止,某个事务尚未完成而被迫中
断,而改未完成的事务对数据库所做的修改已被写入数据库,此时数据库就处于一种不正确(不一
致)的状态。因此划一性是通过原子性来保证的。
[*]其实划一性和用户的业务逻辑强干系,一般MySQL提供技术支持,但是划一性还是要用户业务逻辑
做支持,也就是,划一性,是由用户决定的。
[*]而技术上,通过AID保证C
推荐阅读 :
如何实现事务的隔离性
Innodb中的事务隔离级别和锁的关系
MySQL间隙锁原理

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 【MySQL 保姆级讲授】事务的隔离级别(具体)--下(14)