文章目录
只有InnoDB存储引擎支持事务,所以关于事务的知识点只适用于InnoDB存储引擎。
一、事务的基本概念
事务:由一个SQL语句或一组SQL语句集合组成。事务满足ACID特性:
1. A(atomicity)原子性:
整个事务中的操作,要么全部执行成功,要么全部失败,不可能停滞在中间某个环节。当发生错误时,使用回滚rollback保证全部执行失效,让数据库回到最开始的状态。
【举个栗子】
A向B转账100元,需要执行两个操作:A账户减少100元,B账户增加100元。这两个操作要么都同时执行成功,要么都执行失败,否则一个成功,一个失败都是不合理的。
2. C(consistency)一致性:
在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏,即数据库从一个一致性状态变到另一个一致性状态。
【举个栗子】
A向B转账100元,假设转账之前这两个用户的钱加起来总共是2000,那么A向B转账之后,不管这两个账户怎么转,A用户的钱和B用户的钱加起来的总额还是2000,这个就是事务的一致性。
3. I(isolation)隔离性:
通常来说,事务之间的行为不应该互相影响,但是实际情况中,事务相互影响的程度取决于隔离级别,所以需要 使用隔离级别消除事务间的相互影响,让多个事务互相隔离。
4. D(durability)持久性:
事务提交之后,需要将事务持久化到磁盘,即保证事务执行的结果在磁盘上永久的保存,这样即使系统崩溃,提交的数据也不应该丢失。
【事务的操作】
- 开始:begin或start transaction,开始执行事务。
- 提交:commit,提交事务,表示一次事务执行完毕。
- 回滚:rollback,某个操作执行失败后,回滚到事务执行之前的状态
【一些概念:】
- redo日志:重做日志,记录将要执行的操作,为出现异常情况服务。
- undo日志:未做日志,记录数据每个操作前的状态,为回滚操作服务。
- 日志先行:在开启事务前将日志信息刷新到磁盘上持久化。
二、保证事务的原子性(Atomicity)、一致性(Consistency)、持久性(Durability)
要保证事务的原子性,一致性,持久性,必须达到:
- 事务要么执行成功,要么全部执行失败,进行回滚操作。
- 如果系统崩溃,要保证事务的一致性,即结果不变。
- 将提交的事务持久化到磁盘,即使系统崩溃,提交的数据也不应该丢失。
无法通过加锁操作实现上述要求,因为锁机制是软件机制,当断电后,所有的清零,那么无法保证所有操作执行失败。
采取事务日志系统解决问题,主要有两个日志,redo,undo:
【1. redo log重做日志:】
记录事务将要执行的每一个操作,主要为异常服务。比如:对select,update 50->100,delete等操作都进行记录,表示还没有执行。重做日志完成后,在事务提交前,进行日志先行,将日志信息刷新到磁盘上 。所以当事务开启后,数据库服务器:
- 将所有将要执行的操作记录在重做日志中。
- 记录完成后,进行日志先行,将操作保存在磁盘上,事务将要执行的操作被永久保存。
- 根据磁盘上的数据,一条条进行执行。
- 如果遇到断电或数据库崩溃,加电恢复后,会沿着redo日志的记录点,未完成的事务可以继续提交,也可以选择依据undo日志,进行回滚,把数据库恢复到崩溃前的状态。
【2. undo log未做日志:】
记录事务执行过程中的每一个状态点,当事务提交时,该内容被刷新到磁盘中。主要为事务回滚服务。如select A=10;update:A=10;insert:A=200;假如此时在insert出错了,需要进行回滚,那么沿着undo log未做日志的反方向执行,并不会影响到其他事务的操作。
假如有下面一段事务:
1. begin;
2. 记录 A=1 到undo log;
3. update A = 3;
4. 记录 A=3 到redo log;
5. 记录 B=2 到undo log;
6. update B = 4;
7. 记录B = 4 到redo log;
8. 将redo log刷新到磁盘
9. commit;
把数据库恢复到崩溃前的状态那么有三种情况:
- 在1-8的任意一步系统宕机,事务未提交,该事务就不会对磁盘上的数据做任何影响,所以可以继续沿着redo日志执行。
- 如果在8-9之间宕机,恢复之后可以选择回滚,也可以选择继续完成事务提交,因为此时redo log已经持久化,可以沿着redo log继续提交;undo log中记录了执行状态点,可以进行回滚。
- 若在9之后系统宕机,内存映射中变更的数据还来不及刷回磁盘,那么系统恢复之后 ,可以根据redo log把数据刷回磁盘。
所以,通过两个日志保证了数据的原子性,一致性,持久性,其中:
- redo log重做日志保证了事务的持久性和一致性;通过日志先行和记录执行操作实现。
- undo log未做日志保证了事务的原子性;通过回滚操作支持。
三、不存在隔离性,事务并发产生的问题
隔离性需要保证事务之间互相隔离,如果不存在隔离性,那么就会出现下面的三种情:脏读,不可重复读,幻读。
(一)脏读
脏读:脏读就是指当一个事务正在访问数据,并且对数据进行了修改,这种修改还没有提交到数据库中,这时,另外一个事务也访问了这个数据,如果第一个事务进行回滚操作,将数据修改为初始状态,那么第二个事务获取的数据就是脏数据。 如下图:
解决脏读问题: 让事务执行过程中获取不到其他事务执行过程中的结果。这样B事务在A事务为提交前,不会读取到A事务执行中的结果8000。
(二)不可重复读
不可重复读:事务在执行过程中读取了其他事务不同阶段的结果。是在解决了脏读问题前提下出现的问题。不可重复读是因为修改操作导致。
一个事务,多次读同一数据,在这个事务还没有结束时,另一个事务也访问改同一数据,那么,在第一个事务中两次读数据之间,由于第二个事务的修改,导致第一个事务两次读到的数据可能是不一样的,这就导致了第一个事务两次读到的数据不一样,让数据库产生二义性,哪个都有可能是错误的数据,这就是因为修改操作导致的不可重复读问题,如下图:
解决重复读问题: 如果事务在其他事务执行过程中开启,其他事务相对于该事务透明,即看不到其他事务的执行过程,只能看见其他事务的初始状态。这样B事务读取结果时只会读到5000,因为它看不到A事务提交的结果8000。
(三)幻读
原因:事务执行过程中获取到其他事务不同阶段的结果。因为插入/删除导致幻读,如下图:
解决: 采用间隙锁进行控制,将事务B第一次读取的结果1锁住,最后的结果只能从间隙锁中读取,这样,两次读出结果都是1,解决了幻读。
四、事务的隔离级别
如果不保证事务的隔离性,那么就会出现上述的问题,事务总共有四种程度逐渐加强的隔离级别:
- 未提交读(READ-UNCOMMITTED): 会出现脏读,不可重复读,幻读的问题。
- 已提交读(READ-COMMITTED): 不允许读事务执行过程中的结果,解决脏读问题;会出现不可重复读,幻读。SQL server默认已提交读隔离级别。
- 可重复读(REPEATABLE-READ: 规定其他事务对执行中开启的事务是透明的,解决了不可重复读问题,会出现幻读问题。MySQL默认可重复的读隔离级别,会出现幻读的情况,但是MySQL使用间隙锁处理了幻读的情况,所以MySQL即使在第三隔离级别,也不会出现幻读的情况。
- 可序列化(serializable): 不出现问题,但是处理事务采取串行机制而不是并行,所以效率低下。
可以用一张表总结四种隔离级别:错误❌表示不会出现这种问题,正确 ✅表示会出现这种问题。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
未提交读READ-UNCOMMITTED | ✅ | ✅ | ✅ |
已提交读READ-COMMITTED | ❌ | ✅ | ✅ |
可重复读REPEATABLE-READ | ❌ | ❌ | ✅ |
可串行化 SERIALIZABLE | ❌ | ❌ | ❌ |
【基本操作:】
- 查看当前隔离级别:
select @@tx_isolation;
- 修改隔离级别
set tx_isolation ="隔离级别"; //有四种隔离级别可以选择
MySQL是C/S架构,存在全局隔离级别和当前隔离级别:全局隔离级别:表示数据库服务器默认的隔离级别,当前隔离级别表示客户端的隔离级别。
服务器默认可重复读隔离级别,连接的客户端也默认为此隔离级别。当一个客户端修改了隔离级别,只是修改了当前隔离级别,对其他客户端没有影响。
查看MySQL数据库的默认隔离级别:
是第三级别,可重复读隔离级别。
开启两个客户端A,B,对表test的数据进行操作,保证当前数据库的存储引擎为InnoDB,因为只有InnoDB支持事务:
如果不是,对test表的存储引擎进行修改:
alter table test engine = innodb;
将数据库的隔离级别进行修改,查看不同隔离级别下会出现下列哪些问题:脏读,不可重复读,幻读。
(一)read uncommitted(未提交读)
- 将A,B客户端的隔离级别修改隔离级别为第一隔离级别,未提交读,开启A,B事务:
- A事务,进行查询,查询后将工资修改为8000:
- B事务查询工资,获取到A事务执行中的结果8000:
- A事务回滚rollback,工资变为5000,这时B读到的数据是脏数据:
所以第一隔离级别未提交读,数据库会出现脏读,可重复度,幻读的问题。
(二)read committed(已提交读-SQL server默认隔离级别)
- 将A,B隔离级别改为第二级别,已提交读
- A事务开启,修改工资为8000,B事务查询,现在查询就是5000,因为第二级别隔离,不允许读A事务执行过程中的结果,解决脏读问题:
- A事务提交,薪水变为8000,B事务查询:读到提交后的结果8000,那么B事务两次读取读到了两个数据,数据库产生二义性,产生不可重复读问题:
所以第二隔离级别已提交读隔离级别解决了脏读问题,存在不可重复读,幻读问题。
(三)repeatable read(可重复读-MySQL默认隔离级别)
- 将A,B修改为第三个隔离级别:可重复读
- A事务开启,修改工资为8000,B事务开启,查询为5000,解决了脏读问题。
- A事务提交,B事务查询,还是5000,因为第三隔离级别可重复读隔离级别,A事务对于B事务透明,即B事务看不到A事务的修改等操作,那么就只能获取到初始数值5000,解决了不可重复读问题:
- A事务开启,插入一条数据,插入lisi的薪水为5000,B开启,查询薪水为5000的员工,人数为1个,因为看不到A事务的执行操作:
- A事务提交,B事务查询,如果出现幻读,那么查询结果应该为2,但是此时为1,因为MySQL使用间隙锁解决了幻读的问题,它将B第一次读到的数据1锁住,第二次读到的数据2不能放入锁中,所以最后的结果为1。
所以第三隔离级可重复读解决了脏读,不可重复读问题,但会出现幻读的问题,MySQL利用间隙锁解决了幻读问题。
(四)serializable(串行化)
一般不使用第四种隔离级别,因为它将事务串行处理,根本不会出现脏读,不可重复读,幻读的问题,但是效率低下。
五、伪事务(锁定)
MySQL中,只有InnoDB存储引擎支持事务,其他存储引擎不支持事务,在这种情况下可以使用表锁来代替事务,称为伪事务。
对于不支持事务的存储引擎MyISAM,当用户插入,删除,修改时,这些操作都会保存到磁盘中,当多用户同时操作某个表时,可以使用表锁来避免同一时间有多个用户对数据库中指定表进行操作。 这样可以 避免用户在操作数据表过程中受到干扰,保证了隔离性,但是只有用户释放表锁后,其他用户才可以访问表。
加油哦!🍇。