目录
一、事务的四大特性(ACID)
1. 原子性(atomicity):
2. 一致性(consistency):
3. 隔离性(isolation):
4. 持久性(durability):
二、死锁的产生及解决方法
三、事务的四种隔离级别
0 .封锁协议
1 读未提交(Read uncommitted):
2. 读已提交(Read committed):
3. 可重复读(Repeatable read)
4. 串行化(Serializable)
四、MVCC(Multi-Version Concurrency Control)
1 为什么会有MVCC
2 MVCC在哪个隔离级别下才生效
3 ReadView是什么
五、不同隔离级别演示
演示准备
读未提交----->脏读
读已提交----->不可重复读
可重复读----->幻读
串行化
一、事务的四大特性(ACID)
1. 原子性(atomicity):
事务是一个原子操作,要么全部执行成功,要么全部执行失败。 事务的原子性确保一组逻辑操作,要么全部完成,要么完全不起作用。
2. 一致性(consistency):
事务失败,当前事务都会回滚,事务成功,其他事务可见
3. 隔离性(isolation):
事务的隔离性是指在并发执行的多个事务中,每个事务相互独立,互不影响
事务隔离级别越高,数据冲突的可能性就越小,但并发性能也会受到一定的影响。
4. 持久性(durability):
一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障,应用重启,也不应该对其有任何影响。
二、死锁的产生及解决方法
产生死锁的四个必要条件
- 互斥条件:进程对所分配的资源进行排他性控制,即资源在一段时间内只能被一个进程使用。
- 请求保持条件:一个进程已经获得至少一个资源,但又请求新的资源,而该资源被其他进程占用,导致该进程阻塞,但已获得的资源保持不放。
- 不可剥夺条件:进程已获得的资源在未使用完之前,不能被其他进程强行夺走,只能由获得该资源的进程主动释放。
- 环路等待条件:存在一种循环等待资源的链,链中每个进程已获得的资源同时被链中下一个进程请求,形成循环等待。
数据库死锁产生的原因及解决方案
数据库死锁产生的原因有以下几点:
- 多个事务同时访问同一资源,每个事务都试图获取其他事务已经持有的锁,导致互相等待,从而产生死锁。12
- 事务在获取锁时的顺序不同,也可能导致死锁。例如,事务A获得了资源X的锁,并尝试获取资源Y的锁,而事务B获得了资源Y的锁,并尝试获取资源X的锁。这种情况下,两个事务都无法继续执行,形成了死锁。4
解决数据库死锁的方案有以下几种:
-
保持锁的顺序一致:在多个事务请求资源的情况下,保持锁的请求顺序一致,可以避免死锁的发生。
-
尽量缩短事务的持有时间:事务持有锁的时间过长会增加死锁的风险。因此,在处理事务时,应尽量缩短事务的持有时间,尽快释放锁。
-
使用超时机制:如果一个事务等待锁的时间过长,可以使用超时机制来终止该事务,从而避免死锁的发生。
-
检测并解决死锁:数据库管理系统通常提供死锁检测机制,一旦检测出死锁,可以中止其中一个事务,从而解除死锁。同时,也可以优化锁的使用和重构事务来降低死锁的发生率。如果死锁频繁发生,可能需要增加资源,如提升服务器的处理能力或增加数据库的缓存空间。
三、事务的四种隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | 可能出现 | 可能出现 | 可能出现 |
读已提交 | 不会出现 | 可能出现 | 可能出现 |
可重复读 | 不会出现 | 不会出现 | 可能出现 |
串行化 | 不会出现 | 不会出现 | 不会出现 |
数据库事务的隔离级别有4种,由低到高分别为Read uncommitted 、Read committed 、Repeatable read 、Serializable 。在事务的并发操作中可能会出现脏读,不可重复读,幻读。
0 .封锁协议
一级封锁协议(读未提交):写加写锁,读不加锁。事务T在修改数据R之前必须对其加X锁,直到事务结束才释放。以及封锁协议可以防止修改丢失,并保证事务T是可恢复的。在一级封锁协议中,如果仅仅是读数据不对其进行修改,是不需要加锁的,所以它不能保证可重复读和不读“脏”数据。
二级封锁协议是(读已提交):写加写锁,读加临时锁。一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,读完后即可释放S锁。二级封锁协议除防止丢失修改,还可进一步防止读“脏”数据。在二级封锁协议中,由于读完数据即可释放S锁,所以它不能保证可重复读。
三级封锁协议(可重复读):写加写锁,读加事务锁。一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,直到事务结束才释放。三级封锁协议可以防止丢失修改,读“脏"数据和不可重复读。
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)
它是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率最低,比较耗费数据库性能,一般不推荐使用。
四、MVCC(Multi-Version Concurrency Control)
MVCC原理底层就是通过read view 以及undo log来实现
1 为什么会有MVCC
频繁加锁会导致数据库性能低下,引入了MVCC多版本控制来实现读写不阻塞,提高数据库性能,在多版本并发控制中,为了保证数据操作在多线程过程中,保证事务隔离的机制,降低锁竞争的压力,保证较高的并发量。在每开启一个事务时,会生成一个事务的版本号,被操作的数据会生成一条新的数据行(临时),但是在提交前对其他事务是不可见的,对于数据的更新(包括增删改)操作成功,会将这个版本号更新到数据的行中,事务提交成功,将新的版本号更新到此数据行中,这样保证了每个事务操作的数据,都是互不影响的,也不存在锁的问题。
MVCC机制的实现就是通过read-view机制与undo版本链比对机制,使得不同的事务会根据数据版本链对比规则读取同一条数据在版本链上的不同版本数据。
2 MVCC在哪个隔离级别下才生效
MVCC只在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下工作。其他两个隔离级别和MVCC不兼容,因为 READ UNCOMMITTED 总是读取最新的数据行,而不是符合当前事务版本的数据行。而 SERIALIZABLE 则会对所有读取的行都加锁。
InnoDB行数据默认隐藏列
InnoDB在每行数据都增加三个隐藏字段:一个唯一行号,一个记录创建的版本号,一个记录回滚的版本号。
3 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级别
事务级的快照
版本链比对规则
- 如果被访问版本的trx_id属性值与rv中的creator_trx_id值相同 可见
- 如果被访问版本的trx_id属性值小于rv中的min_trx_id值 可见
- 如果被访问版本的trx_id属性值大于或等于rv中的max_trx_id值 不可见
- 如果被访问版本的trx_id属性值在rv的min_trx_id和max_trx_id之间
4.1 trx_id在m_ids中 不可见
4.2 trx_id不在m_ids中 可见
MVCC比对练习题
在同一个事务里,针对id=1的记录,当前事务ID分别为200,99,38,15,5的行记录能查询到么,是可见的么?
200不可见、99可见、38可见、15不可见、5可见
思考题:为啥要把最小的事务id值单独作为一个字段?
时间复杂度,难道要从所有未提交的集合中去找么,依次遍历
1.版本说明
这里是8.0.33
2. 查询mysql全局事务隔离级别
2.1 查询命令
select @@global.transaction_isolation;
2.2 默认隔离级别如下
3. 注意事项
低版本的查询语句是select @@global.tx_isolation;
五、不同隔离级别演示
演示准备
创建sql语句准备
CREATE TABLE user (--自增IDid 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) 获取行锁更新成功、串行执行
在InnoDB引擎下的的repeatable read (可重复复读)隔离级别下,快照读MVCC影响下,已经解决了幻读的问题