您现在的位置是:首页 >技术教程 >数据库之事务隔离级别详解网站首页技术教程

数据库之事务隔离级别详解

running_1997 2023-07-01 00:00:02
简介数据库之事务隔离级别详解

事务隔离级别详解

一、事务的四大特性(ACID)

1. 原子性(atomicity):

事务是一个原子操作,要么全部执行成功,要么全部执行失败。 事务的原子性确保一组逻辑操作,要么全部完成,要么完全不起作用。

2. 一致性(consistency):

执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的。

3. 隔离性(isolation):

事务的隔离性是指在并发执行的多个事务中,每个事务的执行互不影响,每个事务都有自己独立的空间进行操作。事务隔离级别越高,数据冲突的可能性就越小,但并发性能也会受到一定的影响。

4. 持久性(durability):

一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障,应用重启,也不应该对其有任何影响。

二、事务的四种隔离级别

数据库事务的隔离级别有4种,由低到高分别为Read uncommitted 、Read committed 、Repeatable read 、Serializable 。在事务的并发操作中可能会出现脏读,不可重复读,幻读。

1. 读未提交(Read uncommitted):

1.1 简述

一个事务读到了另一个事务还没有提交的数据。

1.2 事例

父亲要给儿子转账。但是转账时父亲不小心按错了数字,按成10万/月,该钱已经打到儿子的账户,但是事务还没有提交,就在这时,儿子去查看自己的储蓄卡,发现转多了9万,以为凭空多了9万非常高兴。但是父亲及时发现了不对,马上回滚差点就提交了的事务,将数字改成1万再提交。

1.3 分析

实际父亲给儿子转的还是1万,但是儿子看到的是10万。儿子看到的是父亲还没提交事务时的数据。这就是脏读。

2. 读已提交(Read committed):

2.1 简述

一个事务要等另一个事务提交后才能读取数据。

2.2 事例

儿子拿着父亲的信用卡去消费(卡里目前有10),当他买单时(父亲事务开启),收费系统事先检测到他的卡里有10万,就在这个时候!!父亲要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待父亲转出金额事务提交完)。儿子就会很郁闷,明明卡里是有钱的…

2.3 分析

这就是读已提交,若有事务对数据进行更新操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但在这个事例中,出现了一个事务范围内两次相同的查询却返回了不同数据,这就是不可重复读。

3. 可重复读(Repeatable read)

3.1 简述

同一事务下,事务在执行期间,多次读取同一数据时,能够保证读取到的数据是一致的。

3.2 事例

儿子拿着信用卡去享受生活(卡里只有10万),当他买单时(事务开启,不允许其他事务的UPDATE修改操作),收费系统事先检测到他的卡里有10万。这个时候父亲不能转出金额了。接下来收费系统就可以扣款了。

3.3 分析

可重复读解决了不可重复读的问题。说到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。

3.4 什么时候会出现幻读?

3.4.1 简述

一个事务读取到了另一个事务新增的数据

3.4.2 事例

儿子某一天去消费,花了8千元,然后他的父亲去查看他今天的消费记录(全表扫描,儿子事务开启),看到确实是花了8千元,就在这个时候,儿子花了1万买了一部电脑,即新增INSERT了一条消费记录,并提交。当父亲打印儿子的消费记录清单时(儿子事务提交),发现花了1.8万元,似乎出现了幻觉,这就是幻读。

3.4.3 那怎么解决幻读问题?

串行化

4. 串行化(Serializable)

它是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率最低,比较耗费数据库性能,一般不推荐使用。

5. 小结

隔离级别脏读不可重复读幻读
读未提交可能出现可能出现可能出现
读已提交不会出现可能出现可能出现
可重复读不会出现不会出现可能出现
串行化不会出现不会出现不会出现

三、MySql默认隔离级别

1.版本说明

这里是8.0.33

mysql版本说明

2. 查询mysql全局事务隔离级别

2.1 查询命令

select @@global.transaction_isolation;

2.2 默认隔离级别如下

默认隔离级别

3. 注意事项

低版本的查询语句是select @@global.tx_isolation;

四、不同隔离级别演示

演示准备

创建sql语句准备

CREATE TABLE user (
	--自增ID
    id INT NOT NULL AUTO_INCREMENT,
    --姓名
    name VARCHAR(50) NOT NULL,
    --年龄
    age INT NOT NULL,
    --主键
    PRIMARY KEY (id)
);

数据准备

--插入一条记录,id为1,name为'张三',age为20
INSERT INTO user(id, name, age) VALUES (1, '张三', 20);
-- 插入一条记录,id为2,name为'李四',age为30
INSERT INTO user(id, name, age) VALUES (2, '李四', 30);
-- 插入一条记录,id为3,name为'王五',age为25
INSERT INTO user(id, name, age) VALUES (3, '王五', 25);
-- 插入一条记录,id为4,name为'赵六',age为28
INSERT INTO user(id, name, age) VALUES (4, '赵六', 28);

读未提交----->脏读

设置当前会话隔离级别为读未提交

读未提交隔离级别设置

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

1、a 事务(客户端1) 修改 、未提交

在这里插入图片描述

2、b事务(客户端2) 读该数据

在这里插入图片描述

3、a事务(客户端1) 回滚

在这里插入图片描述

4、b事务(客户端2) 再读该数据 和 2步不一致,脏读

在这里插入图片描述

读已提交----->不可重复读

设置当前会话隔离级别为读已提交

在这里插入图片描述

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

1、a事务(客户端1) 修改 、未提交

在这里插入图片描述

2、b事务(客户端2) 读该数据

在这里插入图片描述

3、a事务(客户端1) 提交

在这里插入图片描述

4、b事务(客户端2) 读该数据 , 不可重复读

在这里插入图片描述

可重复读----->幻读

设置当前会话隔离级别为可重复读

在这里插入图片描述

SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

1、a 事务(客户端1) 修改、未提交

在这里插入图片描述

2、b 事务(客户端2) 读该数据

在这里插入图片描述

3、a 事务(客户端1) 提交

在这里插入图片描述

4、b 事务(客户端2) 读该数据 和之前一样可重复读

在这里插入图片描述

串行化

设置当前会话隔离级别为串行化

在这里插入图片描述

SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

1、a 事务(客户端1) 读取id = 1的数据

在这里插入图片描述

2、b 事务(客户端2) 修改id = 1的数据,直接阻塞住

在这里插入图片描述

3、a 事务(客户端1) 提交

在这里插入图片描述

4、b 事务(客户端2) 获取行锁更新成功、串行执行

在这里插入图片描述

五、MVCC(Multi-Version Concurrency Control)

MVCC原理底层就是通过read view 以及undo log来实现

为什么会有MVCC

频繁加锁会导致数据库性能低下,引入了MVCC多版本控制来实现读写不阻塞,提高数据库性能,在多版本并发控制中,为了保证数据操作在多线程过程中,保证事务隔离的机制,降低锁竞争的压力,保证较高的并发量。在每开启一个事务时,会生成一个事务的版本号,被操作的数据会生成一条新的数据行(临时),但是在提交前对其他事务是不可见的,对于数据的更新(包括增删改)操作成功,会将这个版本号更新到数据的行中,事务提交成功,将新的版本号更新到此数据行中,这样保证了每个事务操作的数据,都是互不影响的,也不存在锁的问题。

MVCC在哪个隔离级别下才生效

MVCC只在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下工作。其他两个隔离级别和MVCC不兼容,因为 READ UNCOMMITTED 总是读取最新的数据行,而不是符合当前事务版本的数据行。而 SERIALIZABLE 则会对所有读取的行都加锁。

InnoDB行数据默认隐藏列

InnoDB在每行数据都增加三个隐藏字段:一个唯一行号,一个记录创建的版本号,一个记录回滚的版本号。

ReadView是什么

在我们平时执行一个事务的时候,就会生成一个ReadView,ReadView的组成结构大致如下:
READVIEW

read view 参数解释

1.creator_trx_id: 当前事务id

没什么可解释,就是当前事务ID

2.m_ids:所有活跃事务的事务id

当前所有未提交的事务id构成的集合

3.min_trx_id: m_ids里最小的事务id值

当前所有未提交的事务id构成的集合里的最小的哪个事务id

4.max_trx_id: 最大事务id

下一个即将创建的事务id

Read View快照的生成时机

repeatable read级别

语句级快照

read committed级别

事务级的快照

版本链比对规则

  1. 如果被访问版本的trx_id属性值与rv中的creator_trx_id值相同 可见
  2. 如果被访问版本的trx_id属性值小于rv中的min_trx_id值 可见
  3. 如果被访问版本的trx_id属性值大于或等于rv中的max_trx_id值 不可见
  4. 如果被访问版本的trx_id属性值在rv的min_trx_id和max_trx_id之间
    4.1 trx_id在m_ids中 不可见
    4.2 trx_id不在m_ids中 可见

一句话概括MVCC

MVCC机制的实现就是通过read-view机制与undo版本链比对机制,使得不同的事务会根据数据版本链对比规则读取同一条数据在版本链上的不同版本数据。

MVCC比对练习题

mvcc

在同一个事务里,针对id=1的记录,当前事务ID分别为200,99,38,15,5的行记录能查询到么,是可见的么?

200不可见、99可见、38可见、15不可见、5可见

思考题:为啥要把最小的事务id值单独作为一个字段?

时间复杂度,难道要从所有未提交的集合中去找么,依次遍历

六、Mysql解决了幻读么,当前读呢

在InnoDB引擎下的的repeatable read (可重复复读)隔离级别下,快照读MVCC影响下,已经解决了幻读的问题(因为它是读历史版本的数据),而如果是当前读(指的是 select * from table for update),则需要配合间隙锁来解决幻读的问题。
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。