Mysql 事务及其原理
Mysql 事务及其原理
什么是事务
什么是事务?事务是作为单个逻辑工作单元执行的一系列操作,通俗易懂的说就是一组原子性的 SQL 查询。Mysql 中事务的支持在存储引擎层,MyISAM 存储引擎不支持事务,而 InnoDB 支持,这是 Mysql 5.5.5 以后默认引擎由 MyISAM 换成 InnoDB 的最根本原因。
事务的 ACID 属性
原子性(Atomicity):作为逻辑工作单元,一个事务里的所有操作的执行,要么全部成功,要么全部失败。
一致性(Consistency):数据库从一个一致性状态变换到另外一个一致性状态,数据库的完整性不会受到破坏。
隔离性(Isolation):通常来说,一个事务所做的修改在最终提交前,对其他事务是不可见的。为什么是通常来说,为了提高事务的并发引出不同的隔离级别,具体参考下一章节。
持久性(Durability):一旦事务提交,则其所做的修改就会永久保存到数据库中,即使系统故障,修改的数据也不会丢失。
事务的隔离级别
为了尽可能的高并发,事务的隔离性被分为四个级别:读未提交、读已提交、可重复读和串行化。用户可以根据需要选择不同的级别。
未提交读(READ UNCOMMITTED):一个事务还未提交,它的变更就能被别的事务看到。
例:事务 A 可以读到事务 B 修改的但还未提交的数据,会导致脏读(可能事务 B 在提交后失败了,事务 A 读到的数据是脏的)。
提交读(READ COMMITTED):一个事务提交后,它的变更才能被其他事务看到。大多数据库系统的默认级别,但 Mysql 不是。
例:事务 A 只能读到事务 B 修改并提交后的数据,会导致不可重复读(事务 A 中执行两次查询,一次在事务 B 提交过程中,一次在事务 B 提交之后,会导致两次读取的结果不一致)。
可重复读(REPEATABLE READ):未提交的事务的变更不能被其他事务看到,同时一次事务过程中多次读取同样记录的结果是一致的。 例:事务 A 在执行过程中多次获取某范围内的记录,事务 B 提交后在此范围内插入或者删除 N条记录,事务 A 执行过程中多次范围读会存在不一致,即幻读(Mysql 的默认级别,InnoDB 通过 MVVC 解决了幻读的问题)。
可串行化(SERIALIZABLE):当两个事务间存在读写冲突时,数据库通过加锁强制事务串行执行,解决了前面所说的所有问题(脏读、不可重复读、幻读)。是最高隔离的隔离级别。
用表格可以更清晰的描述四种隔离级别的定义和可能存在的问题:
Mysql 中的事务
1、事务的自动提交
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;
--以上命令可以打开和关闭自动提交
1、通过 set autocommit = 0 关闭当前会话的自动提交,如果需要对全局生效必须再配置文件中进行修改。
2、关闭自动提交后,用户的所有 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:用来设置事务的隔离级别。
2、事务的隔离级别
Mysql 支持事务最流行的存储引擎非 InnoDB 莫属,所以以下的 Mysql 隔离级别设置都是基于 InnoDB 的。
1、查看InnoDB存储引擎系统级的隔离级别和会话级的隔离级别,命令和结果如下:
mysql> select @@global.tx_isolation,@@tx_isolation;
+-----------------------+-----------------+
| @@global.tx_isolation | @@tx_isolation |
+-----------------------+-----------------+
| REPEATABLE-READ | REPEATABLE-READ |
+-----------------------+-----------------+
2、设置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)
3、事务的 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
- 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 则会对所有读取的行都加锁。
4、事务的实现(redo/undo log)
事务的隔离性通过锁或 MVCC 机制来实现,而原子性、持久性和一致性通过 redo/undo log 来完成。redo log 称为重做日志,用来保证事务的原子性和持久性。undo log 称为撤销日志,用来保证事务的一致性。
1、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 开始的日志部分进行恢复。
- 顺序读取及并行应用重做日志。
- 重做日志的应用具有幂等性。
- 重做日志是物理日志,恢复的速度相对较快。
2、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 线程中做最后的删除。
3、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. 提交事务
Mysql 事务及其原理的更多相关文章
- MySQL事务实现原理
MySQL事务隔离级别的实现原理 知识储备 只有InnoDB支持事务,所以这里说的事务隔离级别是指InnoDB下的事务隔离级别 隔离级别 读未提交:一个事务可以读取到另一个事务未提交的修改.这会带来脏 ...
- 深入学习MySQL事务:ACID特性的实现原理
事务是MySQL等关系型数据库区别于NoSQL的重要方面,是保证数据一致性的重要手段.本文将首先介绍MySQL事务相关的基础概念,然后介绍事务的ACID特性,并分析其实现原理. MySQL博大精深,文 ...
- 一文说尽MySQL事务及ACID特性的实现原理
MySQL 事务基础概念 事务(Transaction)是访问和更新数据库的程序执行单元:事务中可能包含一个或多个 sql 语句,这些语句要么都执行,要么都不执行.作为一个关系型数据库,MySQL 支 ...
- MySQL事务原理浅析
前言 因为自己对数据的可靠性,可用性方面特别感兴趣,所以在MySQL事务方面看了很多资料,也看了很多博客,所以想到自己也写一篇博客整理整理自己所学内容,尽量用自己的语言解释得通俗易懂. 事务经典场景 ...
- mysql事务原理及MVCC
mysql事务原理及MVCC 事务是数据库最为重要的机制之一,凡是使用过数据库的人,都了解数据库的事务机制,也对ACID四个 基本特性如数家珍.但是聊起事务或者ACID的底层实现原理,往往言之不详,不 ...
- 面试刷题29:mysql事务隔离实现原理?
mysql的事务是innodb存储引擎独有的,myisam存储引擎不支持事务. 事务最经典的例子就是转账了,事务要保证的是一组数据库的操作要么全部成功,要么全部失败.是为了保证高并发场景下数据的正确性 ...
- MySQL事务(二)事务隔离的实现原理:一致性读
今天我们来学习一下MySQL的事务隔离是如何实现的.如果你对事务以及事务隔离级别还不太了解的话,这里左转. 好的,下面正式进入主题.事务隔离级别有4种:读未提交.读提交.可重复读和串行化.首先我们来说 ...
- 个人MySQL的事务特性原理学习笔记总结
目录 个人MySQL的事务特性原理笔记总结 一.基础概念 2. 事务控制语句 3. 事务特性 二.原子性 1. 原子性定义 2. 实现 三.持久性 1. 定义 2. 实现 3. redo log存在的 ...
- 详解MySQL事务原理
老刘是即将找工作的研究生,自学大数据开发,一路走来,感慨颇深,网上大数据的资料良莠不齐,于是想写一份详细的大数据开发指南.这份指南把大数据的[基础知识][框架分析][源码理解]都用自己的话描述出来,让 ...
随机推荐
- 学习之Redis(一)
一.redis简介 一般学习,最好先去官网,之所以建议看官网,是因为这是一手的学习资料,其他资料都最多只能算二手,一手资料意味着最权威,准确性最高.https://redis.io/topics/in ...
- luogu P1731 [NOI1999]生日蛋糕 |暴力枚举
题目背景 7月17日是Mr.W的生日,ACM-THU为此要制作一个体积为Nπ的M层 生日蛋糕,每层都是一个圆柱体. 设从下往上数第i(1<=i<=M)层蛋糕是半径为Ri, 高度为Hi的圆柱 ...
- java之扩展运算符
java中的扩展运算符为+=.-=./=.%=.*= 当使用扩展运算符时,变量在参与赋值运算时会把结果自动强制转换为当前变量的类型,比如: public class Test{ public stat ...
- Docker学习-jenkins+github实现持续集成和部署
上一篇介绍了docker环境搭建,本篇继续深入,结合jenkins利用docker-compose容器编排简单介绍下如何实现个人学习的持续集成/部署. 本篇学习曲线: 1.安装/运行jenkins容器 ...
- BGA256芯片植球全过程体验(原创)
今天工具到位,迫不亟待,需要对手上的BGA256的FPGA芯片进行植球, 该芯片买来的时候是有球的,只是在焊接后,由于电路板故障或焊接问题,需要拆下来芯片,导致球损失,需要重新植球. 一般植球都是将所 ...
- Chapter 3 :代码的坏味道
"如果尿布臭了,就换掉它." --Beck奶奶,论保持小孩清洁的哲学 代码的坏味道这一章集中论述该何时重构.具体的重构方法在后面的章节. "没有任何度量规矩比得上见识广博 ...
- MySQL创建数据表时设定引擎MyISAM/InnoDB
我在配置mysql时将配置文件中的默认存储引擎设定为了InnoDB.今天查看了MyISAM与InnoDB的区别,在该文中的第七条"MyISAM支持GIS数据,InnoDB不支持.即MyISA ...
- 为什么要使用Unix时间戳
概念: UNIX时间戳:Unix时间戳(英文为Unix epoch, Unix time, POSIX time 或 Unix timestamp) 是从1970年1月1日(UTC/GMT的午夜)开始 ...
- GitHub 上的 12306 抢票神器,助力回家过年
又到周末了,不过本周末有些略微的特殊. 距离每年一次的全球最大规模的人类大迁徙活动已经只剩下一个多月了,各位在外工作一年的小伙伴大多数人又要和小编一样摩拳擦掌的对待史上最难抢的抢票活动. 然鹅,身为一 ...
- 一段简单的关于字符串的 Java 代码竟考察了这么多东西
下面的代码运行结果是什么?解释一下为什么会有这些差异. String s1 = "hello";String s2 = s1 + ",world";String ...