1、事务
1-1、定义
事务会把数据库从一种【一致状态】转换为另一种【一致状态】;
事务是一组不可分组的操作集合,这些操作要么都成功执行,要么都取消执行;
事务是由一系列对系统中数据进行访问与更新操作组成的一个【不可分割】程序执行逻辑单元;
一方面,当多个应用程序【并发访问】数据库时,事务可以提供【隔离】方法,防止不同访问之间相互干扰;
另一方面,事务库在数据库在【事务正确提交(Commit)】或者【异常状态下】仍然能保持数据一致性;
1-2、事务的特性(ACID)
事务是访问并更新数据库各种数据项的一个程序【执行单元】。
在事务操作中,要么都做修改,要么都不做。
事务的4大特性:
- 原子性(A,Atomicity);
- 一致性(C,Consistency);
- 隔离性(I,Isolation);
- 持久性(D,Durability);
1-2-1、原子性(Atomicity)
事务的原子性是指事务必须是一个原子的操作序列单元。
【原子性】指整个数据库事务是【不可分割】的工作单位;
只有使事务中所有的数据库操作都执行成功,才算整个事务成功;
如 取款操作,转账操作,中间任何一步都必须成功;
事务中的各项操作在一次执行过程中,只允许出现两张状态:
- 全部执行;
- 全部不执行;
任何一项操作失败都将导致整个事务失败,同时其他已经被执行的操作都将被撤销并回滚,只有所有的操作全部成功,整个事务才算成功;
1-2-2、一致性(Consistency)
【一致性】指事务将数据库从一种状态转变为下一种【一致的】状态;
在事务开始之前和事务结束之后,数据库的完整性约束没有被破坏;
务的一致性是指事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行前后,数据库都必须处于一致性状态;
事务执行结果必须使数据库从一个一致性状态转变到另一个一致性状态,因此当数据库【只包含成功事务提交结果】时,就能说数据库处于一致性状态;
1-2-3、隔离性(Isolation)
事务的隔离性要求每个读写事务的对象,对其他事务的操作对象相互隔离;
即该事务提交前对其他事务都不可见,通常使用【锁】来实现;
事务的隔离性,是指【在并发环境】中,并发的事务是相互隔离的;
一个事务的执行不能被其他事务干扰;
事务的隔离级别:
- 读未提交(Read Uncommitted);
- 读已提交(Read Committed);
- 可重复读(REPEATABLE READ);
- 串行化(SERIALIZABLE);
1-2-4、持久性(Durability)
事务一旦提交,结果必须是持久的,事务完成对数据的更改不会丢失,即使发生宕机等故障,数据库也能将数据恢复。
持久性保证事务的高可靠性(High Reliability);
对于高可用性,事务本身并不能保证,需要系统配合完成;
2、事务的分类
- 扁平事务(Flat Transactions);
- 带有保存点的扁平事务(Flat Transactions with Save Points);
- 链事务(Chained Transactions);
- 嵌套事务(Nested Transactions);
- 分布式事务(Distributed Transactions);
2-1、扁平事务
在扁平事务中,所有操作处于同一层次,由 BEGIN WORK 开始,由 COMMIT WORK 或 ROLLBACK WORK 结束,期间的操作是【原子的】,要么都执行,要么都回滚;
扁平事务的主要限制是不能提交或者回滚事务的某一部分,或分几个步骤提交;
扁平事务的三种情况:
2-2、带有保存点的扁平事务
允许事务在执行过程中回滚到同一事务中较早的一个状态,因为某些事务可能在执行过程中出现错误,但不会导致所有的操作都无效;放弃整个事务不合要求,开销也大;
保存点(Savepoints)用来通知系统应该记住事务当前的状态,以便之后发生错误时,事务能回到保存点当时的状态;
2-3、链事务(Chained Transaction)
链事务的思想:
在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式地传给下一个要开始的事务;
2-4、嵌套事务
嵌套事务时一个层次结构框架,由一个【顶层事务】控制着各个层次的事务,顶层事务之下嵌套的事务被称为子事务;
具体定义:
1)嵌套事务是由若干事务组成的一棵树,子树既可以是嵌套事务,也可以是扁平事务;
2)处在叶节点的事务是扁平事务;每个子事务从根到叶节点距离可以不同;
3)位于根节点的事务称为【顶层事务】,其他事务称为【子事务】;事务的前驱称为父事务,下一层称为子事务;
4)子事务可以提交也可以回滚;但是它的提交操作并不马上生效,除非其父事务已经提交;任何子事务都在顶层事务提交后才真正提交;
5)树中任意一个事务的回滚会引起它的所有子事务一同回滚,故子事务保留A、C、I特性,不具有D的特性;
2-5、分布式事务
【分布式环境下】的扁平事务;
如跨行转账操作;
3、事务的实现
事务隔离性由【锁】实现;
持久性有 redo log(重做日志) 实现;
原子性和一致性由 undo log 保证;
MySQL事务日志
MySQL 的 InnoDB 事务日志包括 redo log 和 undo log。
redo log(重做日志):通常是物理日志,记录的是数据页的物理修改,而不是某一行或某几行修改成怎样,它用来恢复提交后的物理数据页。
undo log(回滚日志):是逻辑日志,和 redo log 记录物理日志的不一样。
可以这样认为,当 delete 一条记录时,undo log 中会记录一条对应的 insert 记录,当 update 一条记录时,它记录一条对应相反的 update 记录。
MySQL 事务 ACID 特性的实现思想
- 原子性:是使用 undo log 来实现的,如果事务执行过程中出错或者用户执行了 rollback,系统通过 undo log 日志返回事务开始的状态。
- 一致性:因为undo log 用来帮助事务回滚,通过回滚、恢复,以及并发情况下的隔离性(MVCC,读取之前的行版本信息),从而实现一致性。
- 隔离性:通过【锁】使事务相互隔离开;
- 持久性:使用 redo log 来实现,只要 redo log 日志持久化了,当数据库宕机,也可以通过 redo log 把数据恢复。
3-1、redo log
重做日志实现事务的持久性;主要由两部分组成:
- 内存中的重做日志缓冲(redo log buffer),是容易丢失的;
- 磁盘中的重做日志文件(redo log file),是持久的;
3-1-1、原理
InnoDB是事务的存储引擎,其通过【Force Log at Commit】机制实现事务【持久性】;
当事务提交时,必须先将该事务的所有【重做日志】写入到【重做日志文件】进行持久化,待事务的COMMIT操作完成才算完成;
- redo log:用来保证事务的持久性,顺序写,运行时不需要对redo log 进行读取操作;
- undo log:因为需要帮助事务回滚及MVCC功能,undo log需要进行随机读写;
3-1-2、fsync操作
为了确保每次【重做日志】都写入【重做日志文件】(磁盘),在每次将【重做日志缓冲】写入【重做日志文件】后,InnoDB存储引擎都需要调用一次fsync操作;
fsync操作效率取决于磁盘的性能,因此磁盘的性能决定事务提交的性能;
- 性能问题:每次事务提交都需要进行fsync操作;
- 解决方案:事务提交时,不立刻写入重做日志文件,而是等待一段时间后再执行fsync操作;
- 优点:提高数据库性能;
- 缺点:数据库宕机时,日志未写入磁盘,出现丢失事务的问题;
参数innodb_flush_log_at_trx_commit用来控制【重做日志】刷新到磁盘的策略;
值 |
说明 |
性能 |
0 |
事务提交时不写入【重做日志】; |
最好 |
1 |
事务提交时必须进行一次fsync操作; |
最差 |
2 |
事务提交时将重做日志写入【重做日志文件】,但仅写入文件系统的缓存中,不进行fsync操作; 数据库宕机时会丢失为写入文件的事务; |
较好 |
3-1-3、redo log 与 binlog 比较
redo log |
binlog | |
引擎 |
只有 InnoDB引擎 |
任何存储引擎 |
产生位置 |
存储引擎层 |
数据库上层 |
日志内容 |
物理格式日志,记录对于每个页的修改 |
逻辑日志,记录对应的SQL语句 |
记录时间点 |
事务中不断被写入,日志不随事务提交的顺序写入 |
事务提交完成后进行一次写入 |
3-2、undo log
在事务进行回滚使用
Undo log 是 逻辑日志,只是将数据库逻辑地恢复到原来的样子
比如,用户执行了一个INSERT 10W条记录的事务,这个事务会导致分配新的段,即表空间会增加;当执行ROLLBACK后,表空间的大小不会因为回滚而收缩;
除了回滚操作,undo 的另一个作用是MVCC。当用户读取一行记录时,若该记录以及被其他事务占用,当前事务可以通过undo读取之前的行版本信息,以此实现【非锁定读取】;
4、事务的隔离级别
SQL标准定义的四个隔离级别:
- 读未提交:READ UNCOMMITTED;
- 读已提交:READ COMMITTED;
- 可重复读:REPEATABLE READ;
- 序列化:SERIALIZABLE;
InnoDB存储引擎【默认支持】的隔离级别是【REPEATABLE READ】,在【REPEATABLE READ】隔离级别下,使用 【Next-Key Lock】锁的算法,避免幻读产生;
4-1、读未提交
隔离级别最低;
一个事务更新某一数据,但事务还未完成,事务未提交;另一个事务能够访问【未提交】【已更新】的数据;
该隔离级别出现的问题:脏读,不可重复读,幻读;
4-2、读已提交
在一个事务内,多次读取出现前后不一致的情况,即会读取到其它事务【已提交】【更新的】数据;
该隔离级别出现的问题:不可重复读,幻读
4-3、可重复读
在事务处理过程中,多次读取同一个数据时,其值与事务开始时保持一致;
该级别解决了【脏读】和【不可重复读】,但是会出现【幻读】;
4-4、串行化
隔离级别最高;
所有事务都能被串行执行,事务只能一个接一个处理,不能并发执行;
性能较差;
4-5、隔离级别对比
隔离级别 |
脏读 |
可重复读 |
幻读 |
读未提交 |
存在 |
不可以 |
存在 |
读已提交 |
不存在 |
不可以 |
存在 |
可重复读 |
不存在 |
可以 |
存在 |
串行化 |
不存在 |
可以 |
不存在 |
5、分布式事务
5-1、MySQL数据库分布式事务
InnoDB存储引擎利用【XA事务】来支持分布式事务实现;
XA事务允许不同数据库之间的分布式事务;
5-1-1、定义
分布式事务指允许多个独立的【事务资源】参与到一个【全局事务】中;
【全局事务】要求其中所有的事务要么都提交,要么都回滚;
5-1-2、XA事务实现
组成XA事务中的角色:
- 资源管理器(Resource Managers):可以一个或多个;提供访问事务资源的方法,通常一个数据库就是一个资源管理器;
- 事务管理器(Transaction Manager):只有一个;协调参与全局事务中的各个事务,需要和参与【全局事务】的所有【资源管理器】进行通信;
- 应用程序(Application Program):只有一个;定义事务的边界,指定全局事务中的操作;
分布式事务模式
分布式事务【两阶段提交】(Two-Phase Commit)
- 第一阶段,所有参与全局事务的节点都开始准备,告诉事务管理器它们准备好提交了;
- 第二阶段,【**事务管理器】**告诉【资源管理器】执行【COMMIT】还是【ROLLBACK】;如果任何一个节点现实不能提交,则所有的节点都被告知需要回滚;
5-1-3、内部XA事务实现
最常见的内部XA事务存在于binlog与InnoDB存储引擎之间;
在事务提交时,先写二进制日志,再写InnoDB存储引擎的重做日志;
两个操作必须是原子的,即【二进制日志】和【重做日志】必须同时写入;
如图:
如果在完成1、2之后,步骤3之前数据库发生宕机,则会发生主从不一致的情况
解决方案:
1、事务提交时,先做一个【PREPARE】操作,将事务的xid写入日志;
2、接着进行二进制日志的写入(这个阶段会完成资源的加锁,redo log 和 undo log 的写入);
如果在InnoDB存储引擎提交前,MySQL数据库宕机了,那么MySQL数据库在重启后【先检查】准备的UXID事务是否已经提交,若没有,则在【存储引擎层】再进行一次提交操作;