此篇文章算是对mysql事务的一个总结,根本把mysql事务相干的知识点都涵盖到了,面试问来问去无非也就是这些,在理解这些之前咱们先对mysql在执行的过程中 有一个整体的意识,如下图
如上图所示,MySQL服务器逻辑架构从上往下能够分为三层:
(1)第一层:解决客户端连贯、受权认证等。
(2)第二层:服务器层,负责查问语句的解析、优化、缓存以及内置函数的实现、存储过程等。
(3)第三层:存储引擎,负责MySQL中数据的存储和提取。MySQL中服务器层不治理事务,事务是由存储引擎实现的。MySQL反对事务的存储引擎有InnoDB、NDB Cluster等,其中InnoDB的应用最为宽泛;其余存储引擎不反对事务,如MyIsam、Memory等。
具体过程都在图中有所标注,大略看看有个意识就能够了。接下来咱们逐个总结
典型的MySQL事务是如下操作的:
start transaction;…… #一条或多条sql语句commit;
其中start transaction标识事务开始,commit提交事务,将执行后果写入到数据库。如果sql语句执行呈现问题,会调用rollback,回滚所有曾经执行胜利的sql语句。当然,也能够在事务中间接应用rollback语句进行回滚。
主动提交
MySQL中默认采纳的是主动提交(autocommit)模式,如下所示:
mysql> show variables like 'autocommit';+---------------+-------+| Variable_name | Value |+---------------+-------+| autocommit | ON |+---------------+-------+1 row in set (0.00 sec)
在主动提交模式下,如果没有start transaction显式地开始一个事务,那么每个sql语句都会被当做一个事务执行提交操作。
通过如下形式,能够敞开autocommit;须要留神的是,autocommit参数是针对连贯的,在一个连贯中批改了参数,不会对其余连贯产生影响。
mysql> set autocommit =0;Query OK, 0 rows affected (0.00 sec)mysql> show variables like 'autocommit';+---------------+-------+| Variable_name | Value |+---------------+-------+| autocommit | OFF |+---------------+-------+1 row in set (0.00 sec)
如果敞开了autocommit,则所有的sql语句都在一个事务中,直到执行了commit或rollback,该事务完结,同时开始了另外一个事务。
非凡操作
在MySQL中,存在一些非凡的命令,如果在事务中执行了这些命令,会马上强制执行commit提交事务;如DDL语句(create table/drop table/alter/table)、lock tables语句等等。
不过,罕用的select、insert、update和delete命令,都不会强制提交事务。
事务的特点:ACID
原子性(Atomicity)
定义
原子性是指一个事务是一个不可分割的工作单位,其中的操作要么都做,要么都不做;如果事务中一个sql语句执行失败,则已执行的语句也必须回滚,数据库退回到事务前的状态。
简略来说,就是$\color{red}{要么全副失败,要么全副胜利}$
实现原理
在阐明原子性原理之前,首先介绍一下MySQL的事务日志。MySQL的日志有很多种,如二进制日志、谬误日志、查问日志、慢查问日志等,此外InnoDB存储引擎还提供了两种事务日志:redo log(重做日志)和undo log(回滚日志)。其中redo log用于保障事务持久性;undo log则是事务原子性和隔离性实现的根底。
上面说回undo log。实现原子性的要害,是当事务回滚时可能撤销所有曾经胜利执行的sql语句。InnoDB实现回滚,靠的是undo log:当事务对数据库进行批改时,InnoDB会生成对应的undo log;如果事务执行失败或调用了rollback,导致事务须要回滚,便能够利用undo log中的信息将数据回滚到批改之前的样子。
undo log属于逻辑日志,它记录的是sql执行相干的信息。当产生回滚时,InnoDB会依据undo log的内容做与之前相同的工作:对于每个insert,回滚时会执行delete;对于每个delete,回滚时会执行insert;对于每个update,回滚时会执行一个相同的update,把数据改回去。
以update操作为例:当事务执行update时,其生成的undo log中会蕴含被批改行的主键(以便晓得批改了哪些行)、批改了哪些列、这些列在批改前后的值等信息,回滚时便能够应用这些信息将数据还原到update之前的状态。
从上图能够理解到数据的变更都随同着回滚日志的产生:
(1) 产生了被批改前数据(zhangsan,1000) 的回滚日志
(2) 产生了被批改前数据(zhangsan,0) 的回滚日志
依据下面流程能够得出如下论断:
- 每条数据变更(insert/update/delete)操作都随同一条undo log的生成,并且回滚日志必须先于数据长久化到磁盘上
- 所谓的回滚就是依据回滚日志做逆向操作,比方delete的逆向操作为insert,insert的逆向操作为delete,update的逆向为update等。
回滚过程如图
tips:undo log也能够这么了解
当delete一条记录时,undo log中会记录一条对应的insert记录 当insert一条记录时,undo log中会记录一条对应的delete记录当update一条记录时,它记录一条对应相同的update记录
tips:逻辑日志和物理日志的区别
看记日志的时候 是针对一行记录,就是逻辑日志 如果是一个数据页,就是物理日志
持久性(Durability)
定义
事务一旦提交,其所做的批改会永恒保留到数据库中,此时即便零碎解体批改的数据也不会失落。
实现原理:Redo log(WAL write ahead log)
先理解一下MySQL的数据存储机制,MySQL的表数据是寄存在磁盘上的,因而想要存取的时候都要经验磁盘IO,然而即便是应用SSD磁盘IO也是十分耗费性能的。
为此,为了晋升性能InnoDB提供了缓冲池(Buffer Pool),Buffer Pool中蕴含了磁盘数据页的映射,能够当做缓存来应用:
读数据:会首先从缓冲池中读取,如果缓冲池中没有,则从磁盘读取再放入缓冲池;
写数据:会首先写入缓冲池,缓冲池中的数据会定期同步到磁盘中(这一过程称为刷脏);
下面这种缓冲池的措施尽管在性能方面带来了质的飞跃,然而它也带来了新的问题,当MySQL零碎宕机,断电的时候可能会丢数据!!!
因为咱们的数据曾经提交了,但此时是在缓冲池外头,还没来得及在磁盘长久化,所以咱们急需一种机制须要存一下已提交事务的数据,为复原数据应用。
于是 redo log就派上用场了。上面看下redo log是什么时候产生的
既然redo log也须要存储,也波及磁盘IO为啥还用它?
(1)刷脏是随机IO,因为每次批改的数据地位随机,但写redo log是追加操作,属于程序IO。
(2)刷脏是以数据页(Page)为单位的,MySQL默认页大小是16KB,一个Page上一个小批改都要整页写入;而redo log中只蕴含真正须要写入的局部,有效IO大大减少。
redo log与binlog
咱们晓得,在MySQL中还存在binlog(二进制日志)也能够记录写操作并用于数据的复原,但二者是有着基本的不同的:
(1)作用不同:redo log是用于crash recovery的,保障MySQL宕机也不会影响持久性;binlog是用于point-in-time recovery的,保障服务器能够基于工夫点复原数据,此外binlog还用于主从复制。
(2)档次不同:redo log是InnoDB存储引擎实现的,而binlog是MySQL的服务器层(能够参考文章后面对MySQL逻辑架构的介绍)实现的,同时反对InnoDB和其余存储引擎。
(3)内容不同:redo log是物理日志,内容基于磁盘的Page;binlog的内容是二进制的,依据binlog_format参数的不同,可能基于sql语句、基于数据自身或者二者的混合。
(4)写入机会不同:binlog在事务提交时写入;redo log的写入机会绝对多元:
后面曾提到:当事务提交时会调用fsync对redo log进行刷盘;这是默认状况下的策略,批改innodb_flush_log_at_trx_commit参数能够扭转该策略,但事务的持久性将无奈保障。
除了事务提交时,还有其余刷盘机会:如master thread每秒刷盘一次redo log等,这样的益处是不肯定要等到commit时刷盘,commit速度大大放慢。
隔离性(Isolation)
定义
与原子性、持久性侧重于钻研事务自身不同,隔离性钻研的是不同事务之间的相互影响。隔离性是指,事务外部的操作与其余事务是隔离的,并发执行的各个事务之间不能相互烦扰。严格的隔离性,对应了事务隔离级别中的Serializable (可串行化),但理论利用中出于性能方面的思考很少会应用可串行化。
实现原理
隔离性谋求的是并发情景下事务之间互不烦扰。简略起见,咱们仅思考最简略的读操作和写操作(临时不思考带锁读等非凡操作),那么隔离性的探讨,次要能够分为两个方面:
(一个事务)写操作对(另一个事务)写操作的影响:锁机制保障隔离性
(一个事务)写操作对(另一个事务)读操作的影响:MVCC保障隔离性
脏读、不可反复读和幻读
首先来看并发状况下,读操作可能存在的三类问题:
- 脏读:以后事务(A)中能够读到其余事务(B)未提交的数据(脏数据),这种景象是脏读。举例如下(以账户余额表为例)
- 不可反复读:在事务A中先后两次读取同一个数据,两次读取的后果不一样,这种景象称为不可反复读。脏读与不可反复读的区别在于:前者读到的是其余事务未提交的数据,后者读到的是其余事务已提交的数据。举例如下:
- 幻读:在事务A中依照某个条件先后两次查询数据库,两次查问后果的条数不同,这种景象称为幻读。不可反复读与幻读的区别能够艰深的了解为:前者是数据变了,后者是数据的行数变了。举例如下
事务隔离级别
在理论利用中,读未提交在并发时会导致很多问题,而性能绝对于其余隔离级别进步却很无限,因而应用较少。可串行化强制事务串行,并发效率很低,只有当对数据一致性要求极高且能够承受没有并发时应用,因而应用也较少。因而在大多数数据库系统中,默认的隔离级别是读已提交(如Oracle)或可反复读(后文简称RR)。
能够通过如下两个命令别离查看隔离级别:
select @@tx_isolation;+-----------------+| @@tx_isolation |+-----------------+| REPEATABLE-READ |+-----------------+1 row in set (0.00 sec)
MVCC
RR解决脏读、不可反复读、幻读等问题,应用的是MVCC:MVCC全称Multi-Version Concurrency Control,即多版本的并发控制协议。上面的例子很好的体现了MVCC的特点:在同一时刻,不同的事务读取到的数据可能是不同的(即多版本)——在T5时刻,事务A和事务C能够读取到不同版本的数据。
MVCC最大的长处是读不加锁,因而读写不抵触,并发性能好。InnoDB实现MVCC,多个版本的数据能够共存,次要是依附数据的暗藏列(也能够称之为标记位)和undo log。其中数据的暗藏列包含了该行数据的版本号、删除工夫、指向undo log的指针等等;当读取数据时,MySQL能够通过暗藏列判断是否须要回滚并找到回滚须要的undo log,从而实现MVCC;暗藏列的具体格局不再开展。
上面联合前文提到的几个问题别离阐明
脏读
当事务A在T3工夫节点读取zhangsan的余额时,会发现数据已被其余事务批改,且状态为未提交。此时事务A读取最新数据后,依据数据的undo log执行回滚操作,失去事务B批改前的数据,从而防止了脏读。
不可反复读
当事务A在T2节点第一次读取数据时,会记录该数据的版本号(数据的版本号是以row为单位记录的),假如版本号为1;当事务B提交时,该行记录的版本号减少,假如版本号为2;当事务A在T5再一次读取数据时,发现数据的版本号(2)大于第一次读取时记录的版本号(1),因而会依据undo log执行回滚操作,失去版本号为1时的数据,从而实现了可反复读。
幻读
InnoDB实现的RR通过next-key lock机制防止了幻读景象。
next-key lock是行锁的一种,实现相当于record lock(记录锁) + gap lock(间隙锁);其特点是不仅会锁住记录自身(record lock的性能),还会锁定一个范畴(gap lock的性能)。当然,这里咱们探讨的是不加锁读:此时的next-key lock并不是真的加锁,只是为读取的数据减少了标记(标记内容包含数据的版本号等);精确起见权且称之为类next-key lock机制。还是以后面的例子来阐明:
当事务A在T2节点第一次读取0<id<5数据时,标记的不只是id=1的数据,而是将范畴(0,5)进行了标记,这样当T5时刻再次读取0<id<5数据时,便能够发现id=2的数据比之前标记的版本号更高,此时再联合undo log执行回滚操作,防止了幻读。
总结
概括来说,InnoDB实现的RR,通过锁机制、数据的暗藏列、undo log和类next-key lock,实现了肯定水平的隔离性,能够满足大多数场景的须要。不过须要阐明的是,RR尽管防止了幻读问题,然而毕竟不是Serializable,不能保障齐全的隔离,上面是一个例子,大家能够本人验证一下。
一致性
基本概念
一致性是指事务执行完结后,数据库的完整性束缚没有被毁坏,事务执行的前后都是非法的数据状态。数据库的完整性束缚包含但不限于:实体完整性(如行的主键存在且惟一)、列完整性(如字段的类型、大小、长度要符合要求)、外键束缚、用户自定义完整性(如转账前后,两个账户余额的和应该不变)。
实现
能够说,一致性是事务谋求的最终目标:后面提到的原子性、持久性和隔离性,都是为了保障数据库状态的一致性。此外,除了数据库层面的保障,一致性的实现也须要利用层面进行保障。
实现一致性的措施包含:
- 保障原子性、持久性和隔离性,如果这些个性无奈保障,事务的一致性也无奈保障
- 数据库自身提供保障,例如不容许向整形列插入字符串值、字符串长度不能超过列的限度等
- 利用层面进行保障,例如如果转账操作只扣除转账者的余额,而没有减少接收者的余额,无论数据库实现的如许完满,也无奈保障状态的统一