文章目录
- 什么是事务?
- 事务定义:
- 设置事务的目的:
- 事务提交:
- 事物的特性
- 原子性(A)
- 提交分类:
- 一致性(C)
- 持久性(I)
- 持久性危机
- 持久性危机如何解决?
- 为什么用数据缓冲池能保证持久性?
- 隔离性(D)
- 事务的隔离等级
- (1) 读未提交
- (2)读已提交
- (3)可重复读
- (4)串行化
- 隔离级别出现的问题
- 脏读:
- 不可重复读:
- 幻读
————————————————————————————————
什么是事务?
事务是逻辑上的一组操作,要么都执行,要么都不执行。
事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账1000元,这个转账会涉及到两个关键操作就是:将小明的余额减少1000元,将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。
事务就是保证这两个关键操作要么都成功,要么都要失败。
事务定义:
- 事务是一个最小的不可再分的工作单元;通常一个事务对应一个完整的业务(例如银行账户转账业务 ,该业务是一个最小的工作单元)
- 一个完整的业务需要批量的DML(insert、update、delete)语句共同联合完成。
- 事务只和DML语句有关,或者说DML语句才有事务。(这个和业务逻辑有关,业务逻辑不同,DML语句的个数不同。)
设置事务的目的:
保证多条SQL语句要么同时成功,要么同时失败
事务提交:
要达到目的,需要使用事务,事务不能再分,如下:
id money
1 600
2 200
---------------------------------------------
#非事务提交——>提交成功
update action set money = 500 where id = 1;
update action set money = 300 where id = 2;
#非事务提交——>提交失败
update action set money = 500 where id = 1;
update action set money = 300 where id = 2 aa;
-- 如果是同时执行两条SQL语句:
-- 那么其中一条执行成功
-- 但是另一条可以能因为种种原因执行失败
---------------------------------------------
#使用事务——>提交成功
START TRANSACTION;
update action set money = 500 where id = 1;
update action set money = 300 where id = 2;
COMMIT;
#使用事务——>提交失败
START TRANSACTION;
update action set money = 500 where id = 1;
update action set money = 300 where id = 2bb;
COMMIT;
事物的特性
原子性(A)
原子性——>当前事务是不可再分的。事务的原子性确保动作要么全部完成,要么完全不起作用;
提交分类:
- 没有事务——》直接提交
- 有事务——》进行两阶段提交
SQL语句先提交到redolog日志池,日志池中存储的是undolog日志,多条SQL语句先后提交到undolog日志当中,检测到undolog日志没有问题后,再执行commit,再操作数据。
如果undolog日志有问题,就不能修改数据。
一致性(C)
一致性——>事务结束时,数据库完整性不能得到破坏(转账之前两者钱数和为600,转账后也要是600),前边我们实现了原子性、持久性、隔离性都是为了保证一致性。
保证原子性、持久性、隔离性都是为了保证事务的一致性
事务结束时,数据库完整性不能得到破坏(转账之前两者钱数和为600,转账后也要是600)
持久性(I)
持久性——>当我们的事务一旦提交,那么他对数据库的改变就是永久的,即使数据库发生故障也不应该对其有任何影响。
持久性危机
——>假设: SQL提交到undolog日志后,出现未知错误,没有执行commit,此时,由于用户已经提交了SQL语句生成了undolog日志,所以用户会收到成功的消息,但是此时数据库并没有成功修改数据,这就造成了持久性危机。(造成持久性危机是因为两段式提交)
持久性危机如何解决?
——> 在数据提交之前进行刷脏
用户提交SQL语句到undologe日志,执行commit后,将sql语句提交到数据缓冲池,数据池会定时对数据缓冲进行刷脏,执行里面还没有执行的语句。
为什么用数据缓冲池能保证持久性?
因为如果在sql语句生成undolog日志之后出现未知错误,此时用户得到成功消息,数据库未改变,那么等未知错误解决之后,通过定期刷脏,还可以根据undolog日志执行提交的sql语句,这样就解决了持久性危机。
隔离性(D)
隔离性——>并发执行的事务是不能相互干扰的
事务的隔离等级
- 读未提交 read uncommitted
- 读已提交 read committed
- 可重复读 repeatable read
- 串行化 serializable
(1) 读未提交
- 事物A和事物B,事物A未提交的数据,事物B可以读取到。
- 这种隔离级别最低,这种级别一般是在理论上存在,数据库隔离级别一般都高于该级别。
- 三种并发问题都没解决。
如果A读取B后,但是B下一步不执行提交,而是执行回滚,那么就会出现脏读
如何解决脏读??——>读已提交
# 1.创建表:
create table t_user(id int primary key auto_increment,username varchar(255));
# 2.设置读未提交的隔离级别
set global transaction isolation level read uncommitted;
-- 查看当前隔离级别
select @@global.tx_isolation,@@tx_isolation;
-- 注:MySQL8中隔离级别的变量跟之前的版本不一样,
-- 之前是tx_isolation,MySQL8改成了transaction_isolation。
(2)读已提交
- 只能读取到已经提交的数据
- 事物A和事物B,事物A提交的数据,事物B才能读取到
- 这种隔离级别高于读未提交
- 换句话说,对方事物提交之后的数据,我当前事物才能读取到
- 这种级别可以避免“脏数据”
- 这种隔离级别会导致“不可重复读取”
- Oracle默认隔离级别—>读已提交
可以看出,插入但没有提交,是读不出来的,提交之后才能读出来。
解决了脏读问题,产生了不可重复读问题
# 设置读已提交的隔离级别
set global transaction isolation level read committed;
-- 查看当前隔离级别
select @@global.tx_isolation,@@tx_isolation;
(3)可重复读
- 事务A和事务B,事务A提交之后的数据,事务B读取不到
- 事务B是可重复读取数据
- 这种隔离级别高于读已提交
- 换句话说,对方提交之后的数据,我还是读取不到
- 这种隔离级别可以避免“不可重复读取”,达到可重复读取
- MySQL默认级别 ——>可重复读
- 虽然可以达到可重复读取,但是会导致“幻像读”
可见,无论是删除还是添加,commit后都是成功的,但是另一边却还是读出原来的数据,这就是可重复读,读取的是备份数据不是真正的数据。
这样会造成幻读
# 设置可重复读的隔离级别
set global transaction isolation level repeatable read;
-- 查看当前隔离级别
select @@global.tx_isolation,@@tx_isolation;
(4)串行化
事务A和事务B,事务A在操作数据库时,事务B只能排队等待
这种隔离级别很少使用,吞吐量太低,用户体验差
这种级别可以避免“幻像读”,每一次读取的都是数据库中真实存在数据,事务A与事务B串行,而不并发。
# 设置串行化的隔离级别
set global transaction isolation level serializable;
-- 查看当前隔离级别
select @@global.tx_isolation,@@tx_isolation;
隔离级别出现的问题
脏读:
脏读:事务A读取到了事务B修改但是还没有提交的数据
(可以理解为:读取到了undolog日志中的数据,没读取到刷脏后的数据)
如何解决脏读??——>读已提交
不可重复读:
事务A读取到了事务B提交后的数据,对于数据A来说,读取数据在事务B提交前后的结果是不一样的;
即:对于事务A来说,只能读取事务B已经提交的结果,(假设事务A不知道事务B的存在)所以事务A在 事务B还未提交的时候和在事务B提交之后两个时间点,用同样的sql语句读取到了不同的内容,造成不可重复读。
如何解决不可重复读??——>可重复读
幻读
幻读:当事务A要去查询表当中的数据,与此同时第二个事务对表进行了增删改操作,那么对于事务A来说,查询不到事务B的任何操作。(就跟出现了幻觉一样)
如何解决幻读??——>串行化