Mysql 事务-你想知道都在这

什么是事务

什么是事务?事务是做为单个逻辑工做单元执行的一系列操做,通俗易懂的说就是一组原子性的 SQL 查询。Mysql 中事务的支持在存储引擎层,MyISAM 存储引擎不支持事务,而 InnoDB 支持,这是 Mysql 5.5.5 之后默认引擎由 MyISAM 换成 InnoDB 的最根本缘由。java

事务的 ACID 属性

原子性(Atomicity):做为逻辑工做单元,一个事务里的全部操做的执行,要么所有成功,要么所有失败。mysql

一致性(Consistency):数据库从一个一致性状态变换到另一个一致性状态,数据库的完整性不会受到破坏。linux

隔离性(Isolation):一般来讲,一个事务所作的修改在最终提交前,对其余事务是不可见的。为何是一般来讲,为了提升事务的并发引出不一样的隔离级别,具体参考下一章节。面试

持久性(Durability):一旦事务提交,则其所作的修改就会永久保存到数据库中,即便系统故障,修改的数据也不会丢失。sql

事务的隔离级别

为了尽量的高并发,事务的隔离性被分为四个级别:读未提交、读已提交、可重复读和串行化。用户能够根据须要选择不一样的级别。数据库

未提交读(READ UNCOMMITTED):一个事务还未提交,它的变动就能被别的事务看到。缓存

:事务 A 能够读到事务 B 修改的但还未提交的数据,会致使脏读(可能事务 B 在提交后失败了,事务 A 读到的数据是脏的)。安全

提交读(READ COMMITTED):一个事务提交后,它的变动才能被其余事务看到。大多数据库系统的默认级别,但 Mysql 不是。bash

:事务 A 只能读到事务 B 修改并提交后的数据,会致使不可重复读(事务 A 中执行两次查询,一次在事务 B 提交过程当中,一次在事务 B 提交以后,会致使两次读取的结果不一致)。session

可重复读(REPEATABLE READ):未提交的事务的变动不能被其余事务看到,同时一次事务过程当中屡次读取一样记录的结果是一致的。 :事务 A 在执行过程当中屡次获取某范围内的记录,事务 B 提交后在此范围内插入或者删除 N条记录,事务 A 执行过程当中屡次范围读会存在不一致,即幻读(Mysql 的默认级别,InnoDB 经过 MVVC 解决了幻读的问题)。

可串行化(SERIALIZABLE):当两个事务间存在读写冲突时,数据库经过加锁强制事务串行执行,解决了前面所说的全部问题(脏读、不可重复读、幻读)。是最高隔离的隔离级别。

用表格能够更清晰的描述四种隔离级别的定义和可能存在的问题:

以上是对四种隔离级别的定义和初步认识,《 十分钟搞懂MySQL四种事务隔离级别 》这篇文章中有动图能够完全弄明白隔离级别。

Mysql 中的事务

一、事务的自动提交

Mysql 默认采用自动提交(AUTOCOMMIT)模式,也就是说,若是不显示地开始一个事务,则每一个查询都被当作一个事务执行提交操做。 能够经过如下命令查看 mysql 是否打开自动提交,

mysql> show variables like 'AUTOCOMMIT';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.01 sec)
--1 或者 ON 表示启用, 0 或者 OFF 表示禁用
mysql> SET AUTOCOMMIT = 0/1; 
--以上命令能够打开和关闭自动提交
复制代码

一、经过 set autocommit = 0 关闭当前会话的自动提交,若是须要对全局生效必须再配置文件中进行修改。

二、关闭自动提交后,用户的全部 DML 语句都会在同一个事务中,直到遇到 COMMIT 或 ROLLBACK 指令结束事务。

一张动图来讲明以上两点:

固然,用户能够经过 start transaction 或者 begin 显示的开启一个事务。**显示的开启事务会自动执行 set autocommit = 0,并在 commit 或 rollback 结束一个事务后执行 set autocommit = 1。**更多事务的控制语句以下:

START TRANSACTION | BEGIN: 显式地开启一个事务;
COMMIT:也可使用 COMMIT WORK,不过两者是等价的。COMMIT 会提交事务,并使已对数据库进行的全部修改为为永久性的;
ROLLBACK:也可使用 ROLLBACK WORK,不过两者是等价的。回滚会结束用户的事务,并撤销正在进行的全部未提交的修改;
SAVEPOINT identifier:SAVEPOINT 容许在事务中建立一个保存点,一个事务中能够有多个 SAVEPOINT;
RELEASE SAVEPOINT identifier:删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;
ROLLBACK TO identifier:把事务回滚到标记点;
SET TRANSACTION:用来设置事务的隔离级别。
复制代码

二、事务的隔离级别

Mysql 支持事务最流行的存储引擎非 InnoDB 莫属,因此如下的 Mysql 隔离级别设置都是基于 InnoDB 的。

一、查看InnoDB存储引擎系统级的隔离级别和会话级的隔离级别,命令和结果以下:

mysql> select @@global.tx_isolation,@@tx_isolation;
+-----------------------+-----------------+
| @@global.tx_isolation | @@tx_isolation  |
+-----------------------+-----------------+
| REPEATABLE-READ       | REPEATABLE-READ |
+-----------------------+-----------------+
复制代码

二、设置InnoDB存储引擎隔离级别:

语句:

set [ global | session ] transaction isolation level Read uncommitted | Read committed | Repeatable | Serializable;
复制代码

示例:

mysql> set session  transaction isolation level Serializable;
Query OK, 0 rows affected (0.01 sec)

mysql> select @@global.tx_isolation,@@tx_isolation;
+-----------------------+----------------+
| @@global.tx_isolation | @@tx_isolation |
+-----------------------+----------------+
| REPEATABLE-READ       | SERIALIZABLE   |
+-----------------------+----------------+
1 row in set (0.00 sec)
复制代码

三、事务的 MVCC 机制

Mysql 的事务型存储引擎(InnoDB)使用 MVCC(Multi-Version Concurrency Control,多版本并发控制)代替行级锁来提升并发读写的性能。InnoDB 的 MVCC 原理比较简单,它经过在在每行记录后面保存三个隐藏列(事务 id,行的建立的版本号、行的过时版本号)来实现的,下面是 InnoDB 在 REPEATABLE READ 隔离级别下 MVCC 的简化工做原理:

INSERT: InnoDB 为新插入的每一行保存当前系统版本号做为行版本号。

UPDATE: InnoDB为插入一行新记录,保存当前系统版本号做为行版本号,同时保存当前系统版本号到原来的行做为行删除标识。

DELETE: InnoDB为删除的每一行保存当前系统版本号做为行删除标识。

SELECT: InnoDB会根据如下两个条件检查每行记录:

  • InnoDB只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样能够确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。
  • 行的删除版本要么未定义,要么大于当前事务版本号。这能够确保事务读取到的行,在事务开始以前未被删除。

只有符合上述两个条件的记录,才能返回做为查询结果。

下面用更浅显易懂的例子说明 MVCC 下的 INSERT/DELETE/UPDATE/SELECT 操做: 假如 test 表有两个字段 name 和 age;MVCC 的三个隐藏列字段名为 transaction_id、 create_version 和 delete_version。

insert

update

delete

select 知足如下两个条件的记录才能被 select 读取出来:

  • delete_version 未定义或者大于 select 所在事务的 delete_version 的行。
  • create_version 小于或等于 select 所在事务的的 create_version。

经过这个例来看下为何 MVCC 在 REPEATABLE READ 隔离级别下能解决幻读。假若有个事务开始于 update 以后 delete 以前,且结束于 delete 以后,以下:

start transaction;  //假如事务 id = 2.5
select * from test; //执行时间在 update 以后 delete 以前
select * from test; //执行时间在 delete 以后
commit;
复制代码

若是不使用 MVCC 第一条 select * from test 能读到 1 条记录,而 第二条将读取到 0 条记录,同一事务中屡次 select 范围查询读取到的记录不一致即幻读。而使用 MVVC 以后,两条 select 语句读取到的记录相同。

MVCC 只在 REPEATABLE READ 和 READ COMMITTED 两个隔离级别下工做。其余两个隔离级别都和MVCC不兼容,由于 READ UNCOMMITTED 老是读取最新的数据行,而不是符合当前事务版本的数据行。而 SERIALIZABLE 则会对全部读取的行都加锁。

四、事务的实现(redo/undo log)

事务的隔离性经过锁或 MVCC 机制来实现,而原子性、持久性和一致性经过 redo/undo log 来完成。redo log 称为重作日志,用来保证事务的原子性和持久性。undo log 称为撤销日志,用来保证事务的一致性。

一、redo log

基本概念

重作日志用来实现事务的持久性,由如下两部分组成:

  • 重作日志缓冲区(redo log buffer),内存中,易丢失。
  • 重作日志文件(redo log file),磁盘中,持久的。

redo log file 是顺序写入的,在数据库运行时不须要进行读取,只会在数据库启动的时候读取来进行数据的恢复工做。 redo log file 是物理日志,所谓的物理日志是指日志中的内容都是直接操做物理页的命令。重作时是对某个物理页进行相应的操做。

总体流程

更新事务操做一次数据的流程图以下所示:

  • 第一步:先将原始数据从磁盘中读入内存中来,修改数据的内存拷贝。

  • 第二步:生成一条重作日志并写入redo log buffer,记录的是数据被修改后的值。

  • 第三步:在必要的时候,采用追加写的方式将 redo log buffer 中的内容刷新到 redo log file。

  • 第四步:按期将内存中修改的数据刷新到磁盘中。

以上比较重要的是第三步,其中必要的时候有如下几种状况:

  • 事务提交时(最多见的情景,在 commit 以前)
  • 当 log buffer 中有一半的内存空间被使用时
  • log checkpoint 时
  • 实例 shutdown 时
  • binlog切换时
  • 后台线程

写入策略

学过 linux 操做系统的都知道内存中数据写入到磁盘文件中时若是不打开 O_DIRECT 选项。数据是要先写入文件操做系统缓存区中的,而后再某个时刻 flush 到磁盘。流程以下:

事务提交时将 redo log buffer 写入 redo log file,为了保证数据必定能正确同步到磁盘(不只仅只写到文件缓冲区中)文件中,InndoDB 默认状况下调用了 fsync 进行写操做。而 fsync 的性能比较低。固然这只是默认状况,InnoDB 也提供了参数 innodb_flush_log_at_trx_commit 来配置 redo log 刷新到磁盘的策略,有如下三个值:

  • 当设置该值为 1 时,每次事务提交都要作一次 fsync,这是最安全的配置,即便宕机也不会丢失事务;
  • 当设置为 2 时,则在事务提交时只作 write 操做,只保证写到系统的缓冲区,所以实例crash不会丢失事务,但宕机则可能丢失事务;
  • 当设置为 0 时,事务提交不会触发 redo 写操做,而是留给后台线程每秒一次的刷盘操做,所以实例 crash 将最多丢失一秒钟内的事务。

用下图能够更直观的说明 innodb_flush_log_at_trx_commit 不一样值下的不一样策略。操做越接近磁盘性能越低,固然可靠性愈来愈高。故性能:1 < 2 < 0,可靠性:0 < 2 < 1。

当 innodb_flush_log_at_trx_commit 设置为 0 或者 2 时丧失了事务的 ACID 特性,一般在平常环境时将其设置为 1,而在系统高峰时将其设置为 2 以应对大负载。

恢复

InnoDB 引擎启动时无论上次数据库运行时是否正常关闭,都会尝试进行恢复操做。整个恢复操做有以下特色:

  • 从 checkpoint 开始的日志部分进行恢复。
  • 顺序读取及并行应用重作日志。
  • 重作日志的应用具备幂等性
  • 重作日志是物理日志,恢复的速度相对较快。
二、undo log

基本概念

  • undo log 用来实现事务的一致性,InndoDB 能够经过 redo log 对页进行重作操做。可是有时候事务须要进行回滚,这时就须要 undo log。
  • undo log 还可能够用来协助 InnoDB 引擎实现 MVCC 机制。
  • undo log 是逻辑日志,恢复时并非对物理页直接进行恢复,而是逻辑地将数据库恢复到原来的样子。
  • undo log 的产生也会伴随着 redo log 的产生。

写入时机

事务开始以前,流程如图:

日志格式

在 InnoDB 中 undo log 分为 insert undo log 和 update undo log

insert undo log

insert 操做产生的日志。根据隔离性,insert 插入的记录只对本事务可见,因此事务提交后能够删除因 insert 产生的日志。

update undo log

delete 和 update 操做产生的日志。根据前面的 MVCC 机制能够知道此部分记录还有可能要被其余事务所使用,因此即便事务提交也不能删除相应的日志。在事务提交时会被保存到 undo log 链表,在 purge 线程中作最后的删除。

三、redo 与 undo log 记录过程

undo 记录更新以前的日志,为了回滚。 redo 记录更新以后的日志,为了重作。 redo log 与 undo log 产生过程的简化版本以下,能够更方便的理解 redo 与 undo 的区别。

假设有A,B两个数据,原值分别为 1, 2;现将A更新为10,B 更新为 20;undo log记录信息过程以下:
1. 事务开始
2. 记录 A = 1 到 undo log
3. 更新 A = 10
4. 记录 A = 10 到 redo log
5. 记录 B = 2 到 undo log
6. 更新 B = 20
7. 记录 B = 20 到 redo log
8. redo log 信息写入磁盘
9. 提交事务
复制代码

总结

比起索引事务或许并不那么耀眼,但事务也是数据库中比较重要的一部分。不少时候咱们只是简单的使用一些命令来进行事务操做,甚至在 Spring 框架中只须要一个注解便可搞定。但对其背后的原理并不必定理解,本篇文章对最经常使用的数据库 Mysql 的事务作了一次全面的总结,但愿能够帮助你们更系统的理解事务。这样不管是在和同事讨论时、应付面试时均可以侃侃而谈。

参考