文章目录
- 面试题
- 事务
- 1.概念
- 2.事务的四大特性ACID
- 3.操作
- 并发事务问题
- 1.脏读
- 2.不可重复读
- 3.幻读
- 事务隔离级别(解决并发问题)
- 事务的原理
- 1.redo log(重做日志)
- 2.undo log(回滚日志)
- 3.MVCC(多版本并发控制)
面试题
1.并行事务会引发什么问题?
2.事务有哪几个特性?
3.事务的原理/事务的特性是怎么实现的?
4.事务的隔离级别有哪些?
事务
1.概念
数据库的事务(Transaction)是一种机制、一个操作序列,包含了一组数据库操作命令。
事务把所有的命令作为一个整体一起向系统提交或撤销操作请求,即这一组数据库命令要么同时成功,要么同时失败。
事务是一个不可分割的工作逻辑单元。
语法:
操作 | 含义 |
---|---|
开启事务 | START TRANSACTION 或 BEGIN ; |
提交事务 | COMMIT; |
回滚事务 | ROLLBACK |
默认MySQL的事务是自动提交的,当执行完一条DML语句时,MySQL会立即隐式的提交事务。(当执行上面开启事务语句时,需手动提交事务)
2.事务的四大特性ACID
-
原子性(Atomicity): 事务是不可分割的最小操作单位,要么同时成功,要么同时失败
-
一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态(比如转账问题中事务开启前的金钱总额和开启后的金钱总额要一致)
-
隔离性(Isolation):(由于数据库允许多个并发事务同时对其数据进行读写和修改的能力),数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
-
持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的
3.操作
最经典的例子就是转账问题,如果张三和李四的账户各有1000块钱,现在李四给张三账户转账500,那么转账成功张三账户应该是1500,李四账户是500.
但是如果没有开启事务,在整个操作中发生了异常,就会导致李四转账操作成功,但是张三账户增加操作没成功。
从代码角度理解:
(1)先是正常情况
-- 1. 查询张三余额
select * from account where name = '张三';
-- 2. 张三的余额减少1000
update account set money = money - 1000 where name = '张三';
-- 3. 李四的余额增加1000
update account set money = money + 1000 where name = '李四';
(2)异常情况(转账中出现异常情况)
-- 1. 查询张三余额
select * from account where name = '张三';
-- 2. 张三的余额减少1000
update account set money = money - 1000 where name = '张三';
出错了....
-- 3. 李四的余额增加1000
update account set money = money + 1000 where name = '李四';
此时第三个语句因为前面“出错了……”这句异常导致没有执行,所以最后的结果就是不成功的。
(3)所以要利用事务
-- 开启事务
start transaction
-- 1. 查询张三余额
select * from account where name = '张三';
-- 2. 张三的余额减少1000
update account set money = money - 1000 where name = '张三';
-- 3. 李四的余额增加1000
update account set money = money + 1000 where name = '李四';
-- 如果正常执行完毕, 则提交事务
commit;
-- 如果执行过程中报错, 则回滚事务
-- rollback;
出现异常的时候,回滚事务后,就可以恢复,如下图所示:
并发事务问题
MySQL 服务端是允许多个客户端连接的,这意味着 MySQL 会出现同时处理多个事务的情况。也就有一系列问题。
1.脏读
一个事务读到另外一个事务还没有提交的数据。
解释:
一个事务读取数据并且对数据进行了修改,这个修改对其他事务来说是可见的,即使当前事务没有提交。这时另外一个事务读取了这个还未提交的数据,但第一个事务突然回滚,导致数据并没有被提交到数据库,那第二个事务读取到的就是脏数据
例如:事务 1A读取某表中的数据 A=20,事务 1 修改 A=A-1,事务 2 读取到 A = 19,事务 1 回滚导致对 A 的修改并为提交到数据库, A 的值还是 20。
2.不可重复读
一个事务先后读取同一条记录,但两次读取的数据不同,称之为不可重复读
一个事务先后读取同一条记录,但两次读取的数据不同。事务2两次读取同一条记录,但是读取到的数据却是不一样的
3.幻读
一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现了 “幻影”。
那么怎么解决这些并发事务呢,引出下面的事务隔离级别
事务隔离级别(解决并发问题)
主要是下面四种隔离级别:读未提交,读已提交,可重复读,可串行化
MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)(也就是说可以避免脏读和不可重复读)
注意:事务隔离级别越高,数据越安全,但是性能越低。
语法:
操作 | 含义 |
---|---|
SELECT @@TRANSACTION_ISOLATION; | 查看事务隔离级别 |
SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL { READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE } | 设置事务隔离级别 |
事务的原理
那么InnoDB 引擎通过什么技术来保证事务的这四个特性的呢?
持久性是通过 redo log (重做日志)来保证的;
原子性是通过 undo log(回滚日志) 来保证的;
隔离性是通过 MVCC(多版本并发控制) + 锁机制来保证的;
一致性则是通过持久性+原子性+隔离性来保证;
1.redo log(重做日志)
重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性。
该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log file),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志文件中, 用于在刷新脏页到磁盘,发生错误时, 进行数据恢复使用。
2.undo log(回滚日志)
回滚日志,用于记录数据被修改前的信息 , 作用包含两个 : 提供回滚(保证事务的原子性) 和MVCC(多版本并发控制) 。
undo log和redo log记录物理日志不一样,它是逻辑日志。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的 update记录。当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚
3.MVCC(多版本并发控制)
维持一个数据的多个版本,使得读写操作没有冲突。用来解决读-写冲突的无锁并发控制
(1)基本概念
当前读
读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。对于我们日常的操作,如:select … lock in share mode(共享锁),select …for update、update、insert、delete(排他锁)都是一种当前读。
快照读
简单的select(不加锁)就是快照读,快照读,读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。
(2)隐藏字段
(3)undo log 版本链
不同事务或相同事务对同一条记录进行修改,会导致该记录的undolog生成一条
记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。
(4)ReadView
ReadView(读视图)是 快照读 SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务(未提交的)id。
包含四个核心字段:
而在readview中就规定了版本链数据的访问规则:trx_id 代表当前undolog版本链对应事务ID。
(5)实现原理
不同的隔离级别,生成ReadView的时机不同:
- READ COMMITTED :在事务中每一次执行快照读时生成ReadView。
- REPEATABLE READ:仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView(所以最终快照读返回的结果也是一样的。)
参考链接:https://javaguide.cn/database/
https://www.bilibili.com/video/BV1Kr4y1i7ru?p=145&vd_source=752a4cd440b20a1953dc8d254ef99696