前言
什么是事务
数据库事务(transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。
首先我们需要明确几个前提:
-
如果执行多条SQL语句,这些语句对数据库的操作不是原子性的。
-
一个事务分为:开启、执行、提交三个阶段。
-
只有事务提交了数据才算做有效。
为什么要有事务
数据库事务存在的意义就是保证对数据库操作的准确性 , 保证数据库操作准确性的便是事务的
四大特性
原子性:事务开始后的所有操作要么成功要么失败
一致性:事务的一致性是指事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处以一致性状态。
隔离性:事务的隔离性是指在并发环境中,并发的事务是互相隔离的,一个事务的执行不能被其它事务干扰。也就是说,不同的事务并发操作相同的数据时,每个事务都有各自完整的数据空间。
持久性:事务的持久性是指事务一旦提交后,数据库中的数据必须被永久的保存下来。即使服务器系统崩溃或服务器宕机等故障。只要数据库重新启动,那么一定能够将其恢复到事务成功结束后的状态
正文
实际上,我们研究事务的原理,就是研究MySQL的InnoDB引擎是如何保证事务的四大特性的。
而对于这四大特性,实际上分为两个部分。 其中的原子性、一致性、持久性,实际上是由InnoDB中的两份日志来保证的,一份是redo log日志,一份是undo log日志。 而隔离性是通过数据库的锁, 加上MVCC来保证的
下面着重说下这两个日志的作用
一、重做日志 redo log
1. 作用
记录事务提交时数据页的物理修改, 用来实现事物的持久性 .当事务提交之后会把所有修改信息都存到该日志文件中, 用于在刷新脏页到磁盘,发生错误时, 进行数据恢复使用。
2. 组成
- 重做日志缓冲(redo log buffer), 内存中
- 重做日志文件(redo log file), 磁盘中
3. 为什要用 redo log
在探讨为什么要用 redo log 之前我们要先去想如果没有redo log 会出现哪些问题 ?
首先我们需要知道, 在InnoDB引擎中的内存结构中, 主要的内存区域是 缓冲池 , 在缓冲池中缓存的很多数据页 .
当我们在一个事务中执行了增删改查时 , InnoDB引擎会首先操作缓冲池中的数据, 此时如果缓冲池中没有对应的数据 , InnoDB引擎会通过后台线程将磁盘中的数据加载到缓冲区 , 然后在去修改数据, 被修改后的数据页 , 称之为 "脏页" . 而脏页会在一定的时机 , 同样是通过后台线程刷新到磁盘中, 从而保证了缓冲区与磁盘的数据是一致的 . 但是 !!! 缓冲区的数据并不是实时刷新的 , 那么问题就出现了 , 假如在缓冲区刷新数据到磁盘中的过程出错了, 事务却显示成功了 .
解决办法, 就是引入redo log的使用
有了redoog之后, 当对缓冲区的数据进行整删改查之后 , 会首先将数据页的操作 , 记录在redo log buffer中, 在事务提交时 , 会将redo log buffer 中的数据刷新到redo log 磁盘文件中, 如果之后在进行将缓冲区的脏页刷新到磁盘时候发生错误 , 就可以接住 redo log 进行数据恢复 , 以此来保证事务的正确性 .
二、回滚日志 undo log
1. 作用
用于记录数据被修改前的信息 , 可提供回滚 , 和 MVCC(多版本并发控制).
undo log 与 redo log 记录物理日志不一样 , 他是逻辑日志 . 可以认为 当del一条数据时候 , undo log 中会记录一条对应的insert 日志 反之亦然, 当进行修改的时候 , 它同样会修改一条相反的修改sql 记录, . 所以当需要荣rollback时候 , 就可以从undo log 中读取内容进行回滚.
Undo log销毁:undo log在事务执行时产生,事务提交时,并不会立即删除undo log,因为这些日志可能还用于MVCC。
Undo log存储:undo log采用段的方式进行管理和记录,存放在rollback segment回滚段中,内部包含1024个undo log segment。
三、MVCC
1. 基本概念
(1)当前读
读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。对于我们日常的操作,如:select ... lock in share mode(共享锁),select ... for update、update、insert、delete(排他锁)都是一种当前读。
(2)快照读
简单的select(不加锁)就是快照读,快照读,读取的是记录数据的可见版本,有可能是历史数据, 不加锁,是非阻塞读。
- Read Committed:每次select,都生成一个快照读。
- Repeatable Read:开启事务后第一个select语句才是快照读的地方。
- Serializable:快照读会退化为当前读。
(3)MVCC
全称 Multi-Version Concurrency Control,多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,快照读为MySQL实现MVCC提供了一个非阻塞读功能。MVCC的具体实现,还需要依赖于数据库记录中的三个隐式字段、undo log日志、readView。
2. 实现原理
(1)隐藏字段
实际上除了自己创建的字段以外,InnoDB还会自动的给我们添加三个隐藏字段及其含义分别是:
隐藏字段 | 含义 |
DB_TRX_ID | 最近修改事务 ID ,记录插入这条记录或最后一次修改该记录的事务 ID 。 |
DB_ROLL_PTR | 回滚指针,指向这条记录的上一个版本,用于配合 undo log ,指向上一个版本。 |
DB_ROW_ID | 隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段。 |
而上述的前两个字段是肯定会添加的, 是否添加最后一个字段DB_ROW_ID,得看当前表有没有主键,如果有主键,则不会添加该隐藏字段
(2)undo log
回滚日志,在insert、update、delete的时候产生的便于数据回滚的日志。
当insert的时候,产生的undo log日志只在回滚时需要,在事务提交后,可被立即删除。
而update、delete的时候,产生的undo log日志不仅在回滚时需要,在快照读时也需要,不会立即被删除。
undo log版本链 : 不同事务或相同事务对同一条记录进行修改,会导致该记录的undo log生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。
ReadView(读视图)是 快照读 SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务(未提交的)id
ReadView中包含了四个核心字段:
字段 | 含义 |
m_ids | 当前活跃的事务 ID 集合 |
min_trx_id | 最小活跃事务 ID |
max_trx_id | 预分配事务 ID ,当前最大事务 ID+1 (因为事务 ID 是自增的) |
creator_trx_id | ReadView 创建者的事务 ID |
而在readview中就规定了版本链数据的访问规则:
trx_id 代表当前undolog版本链对应事务ID。
条件 | 是否可以访问 | 说明 |
trx_id ==creator_trx_id | 可以访问该版本 | 成立,说明数据是当前这个事务更改的。 |
trx_id < min_trx_id | 可以访问该版本 | 成立,说明数据已经提交了。 |
trx_id > max_trx_id | 不可以访问该版本 | 成立,说明该事务是在ReadView生成后才开启。 |
min_trx_id <= trx_id<= max_trx_id | 如果 trx_id 不在 m_ids 中,是可以访问该版本的 | 成立,说明数据已经提交。 |
不同的隔离级别,生成ReadView的时机不同:
- READ COMMITTED :在事务中每一次执行快照读时生成ReadView。
- REPEATABLE READ:仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView
所以呢,MVCC的实现原理就是通过 InnoDB表的隐藏字段、UndoLog 版本链、ReadView来实现的。而MVCC + 锁,则实现了事务的隔离性。 而一致性则是由redolog 与 undolog保证。