前言
事务(Transaction)是数据库学习中非常重要的一种概念,作为关系型数据库的核心组成,在数据安全方面有着非常重要的作用。
事务在各大数据库中都有非常广泛的应用,并且对于很多业务,例如电商、支付,是保证其可以正常运行的根基。
本文会逐步解析数据库事务的核心特性,以获得对事务更深的理解,主要以MySql的InnoDB引擎来讲解。
希望对各位有所帮助,觉得不错可以给点个赞哦~
事务的定义
事务是一个数据库操作序列,由事务开始与事务结束之间执行的全部数据库操作组成,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。
白话讲就是,要么做完,要么别做,做一半了别想跑,你得给把烂摊子收好再走人!
事务中的四大特性:ACID
小案例
什么是ACID,即:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
来看一个银行转账的案例简单体会一下,然后我们再来详细解释
小明用网银给小红转账100元,在这种交易的过程中,有几个问题需要思考
- A:如何同时保证,小明总金额减少100元,小红总金额增加100元?
- C:小明正在转账时,小李用小明的卡在ATM提走了全部的钱,小明还能转账成功吗?
- I:小明正在转账时,小李用小明的卡查询小明的余额,小明的钱少没少?
- D:如果数据库突然崩溃,保证交易数据会不会成功保存在数据库中?
ACID
原子性
一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。
事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
一致性
事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。
如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。
如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。
一致性依赖于原子性和隔离性。
隔离性
指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。
由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。
事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
持久性
指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。
即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。
回到案例
- 原子性确保了不管交易过程中发生了什么意外状况,小明和小红的金额变动要么同时成功,要么同时失败
- 一致性确保钱不会在系统内凭空产生或消失, 所以小明的转账是会被退回的
- 隔离性保证了在事务提交之前,其他事务是无法得知其内部变化的,所以小李检查时钱不会少
- 持久性确保如果事务刚刚提交,数据库就发生崩溃,执行的结果依然会保存在数据库中
ACID根本问题,是针对不同的事务同时对同一份数据进行写操作的。
实现原理
InnoDB是MySql中唯一支持事务的存储引擎,那么它是如何实现ACID的呢?
主要通过两门技术:并发控制技术和日志恢复技术
并发控制技术保证了事务的隔离性,使数据库的一致性不会因为并发执行的操作被破坏。
日志恢复技术保证了事务的原子性,使一致性、持久性不会因事务或系统故障被破坏。
并发控制
主要就是上锁,流程如下:
- 先获得了锁,然后才能修改对应的数据A
- 事务完成后释放锁,给下一个要修改数据A的事务
- 同一时间,只能有一个事务持有数据A的互斥锁
- 没有获取到锁的事务,需要等待锁释放
日志恢复
-
Redo Log:
Redo Log记录的是新数据的备份。在事务提交前,只要将Redo Log持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是Redo Log已经持久化。系统可以根据Redo Log的内容,将所有数据恢复到最新的状态。
-
Undo Log:
Undo Log是旧数据的备份,在操作任何数据之前,首先将数据备份到Undo Log,然后进行数据的修改。如果出现了错误或者用户执行了回滚语句,系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态。
事务的隔离级别
前面提到了,数据库的隔离性是依靠并发控制来实现的,也就是通过加锁来实现的。
数据库是个高并发的应用,同一时间会有大量的并发访问,如果加锁过度,会极大的降低并发处理能力。
所以对于加锁的处理,就成了数据库的重中之重。
一次封锁锁/两段锁
一次封锁法,就是在方法的开始阶段,已经预先知道会用到哪些数据,然后全部锁住,在方法运行之后,再全部解锁。
这种方式是最为熟知和常用的方法,可以有效的避免循环死锁。
一次封锁法在数据库中并不好用,因为在事务开始阶段,数据库并不知道会用到哪些数据。
数据库采用的是两段锁,将事务分成两个阶段:
- 加锁阶段:
- 在对任何数据进行读操作之前要申请并获得S锁,即共享锁,其它事务可以继续加共享锁,但不能加排它锁。
- 在进行写操作之前要申请并获得X锁,即排它锁,其它事务不能再获得任何锁。
- 加锁不成功,则事务进入等待状态,直到加锁成功才继续执行。
- 解锁阶段:当事务释放了一个封锁以后,事务进入解锁阶段,在该阶段只能进行解锁操作不能再进行加锁操作。
事务 | 加锁/解锁处理 |
---|---|
begin; | |
insert into test ….. | 加insert对应的锁 |
update test set… | 加update对应的锁 |
delete from test …. | 加delete对应的锁 |
commit; | 事务提交时,同时释放insert、update、delete对应的锁 |
两段锁无法避免死锁,但是两段锁协议可以保证事务的并发调度是串行化,关于穿行化后面会进行解释。
四种隔离级别
隐式事务,即单条语句。
前置概念
了解隔离级别之前,我们需要先了解三个概念,脏读、幻读、不可重复读。
脏读
所谓脏读是指一个事务中访问到了另外一个事务未提交的数据,如图:
不可重复读
一个事务读取同一条记录2次,得到的结果不一致,如图:
幻读
一个事务读取2次,得到的记录条数不一致,如图:
隔离级别
在数据库操作中,为了有效保证并发读取数据的正确性,提出的事务隔离级别。
我们的数据库锁,也是为了构建这些隔离级别存在的。
隔离级别 | 脏读(Dirty Read) | 不可重复读(NonRepeatable Read) | 幻读(Phantom Read) |
---|---|---|---|
未提交读(Read uncommitted) | ✅ | ✅ | ✅ |
已提交读(Read committed) | ❌ | ✅ | ✅ |
可重复读(Repeatable read) | ❌ | ❌ | ✅ |
可串行化(Serializable ) | ❌ | ❌ | ❌ |
可重复读是InnoDB默认级别
总结
-
事务的 ACID 四大基本特性在很大程度上保证了数据库的安全运行。
-
虽然ACID有很多好处,但是也有一些缺点,尤其是隔离性会对性能有比较大影响,所以在实际的使用中我们也会根据业务的需求对隔离性进行调整。