Mysql事务

一、事务概述

我们可以将事务理解为一组sql语句的集合。事务可以只包含一条sql,也可以包含多条sql,事务中所有sql语句被当作一个操作单元,要么全部执行成功,要么全部失败。
mysql中 innodb存储引擎支持事务,MyISAM不支持事务。并且Innodb存储引擎的事务完全符合ACID特性。
ACID的理论基础:
1、原子性:atomicity (整个事务中的所有操作要么全部成功,要么全部失败回滚到最初状态)
2、一致性:consistency (数据库从一个一致性性状态转为另一个一直性状态)
3、隔离性:isolation (一个事务在提交之前所作出的操作是否能被其他事务可见,不同场景需求不同,因此有多个隔离级别)
4、持久性:durability (事务一旦提交,事务所做出的修改将会永久保存,即使此时数据库崩溃,修改数据也不会消失)

在讲事务例子时多数都以银行转账作为场景

例子:A账户余额 2000,B账户余额 1000 ,现在A用户要向B用户转账200元,那么转账之后A账户余额应该有1800,B账户余额应该有1200。这种情况看似没有问题,但是如果不用事务可能会出现 A账户少了200,但是B账户并没有加上200。
使用事务后整个过程如下:
事务开始
update A账户余额-200
update B账户余额+200
提交事务 (事务结束)

事务底层是怎样实现的呢? 通过 “事务日志” 来实现的这种功能。事务日志分为 redo log 和 undo log 。

(1)、redo log
mysql会将事务中的sql语句涉及到的所有数据操作先记录到redo log中,然后再将操作从redo log中同步到对应数据文件中。也就是说,在事务执行提交成功之前,在修改对应的数据文件中的记录之前,一定要保证对应的所有修改操作已经记录到 redo log中。假设事务中的sql语句涉及到10条记录的修改,那么在修改这10条记录之前,要将这10条修改操作记录记录到 redo log中,当这10条操作都记录到 redo log中以后再从redo log中一条一条同步到数据文件的对应记录中。所以即使数据文件中的数据被修改到一半时被打断 (比如断电),那么在恢复后也能依靠redo log日志将剩余部分操作再次同步到对应的数据文件中。

使用redo log,能够实现ACID中的原子性。redo log 其实由两部分组成:redo log buffer(重做日志缓冲)和 redo log file (重做日志文件) redo log buffer存在于内存之中,容易丢失,redo log file 是持久的,存在于硬盘上。
--------------> log buffer --------------> os buffer----------------->redo log file ----------------------->数据文件
重做日志先被写入到 redo log buffer 中,虽然内存的速度极快,但是无法满足持久性的需求,因为内存数据容易丢失,所以为了满足持久性,需要将redo log buffer中的日志写入到redo log file 中,相当于从内存同步到磁盘,所以磁盘的性能会影响事务的性能,由于 redo log file 是磁盘上一系列连续的空间,所以相对于离散的空间写速度还是比较快,当操作被记录到 redo log file中以后再从 redo logfile中将操作同步到数据文件中。
虽然,我们应该实时将 redo log buffer 中的数据写入到 redo log file 中以保证数据的安全性,但是这样会极大的降低性能,我们可以通过设置 innodb_flush_log_at_trx_commit 参数来修改 redo log buffer 写入 redo log file的策略,但是这样会丧失持久性,有可能丢失部分数据,具体怎么选择,需要根据不同的业务场景自己权衡利弊。

redo log 是物理日志,之所以说是物理日志,是因为 redo log 中记录的是数据库对页的操作,而不是逻辑上的增删改查,重做日志具有幂等性。

(2)、undo log
我们可以把undo log理解成数据被修改前的备份,如果说事务进行了一半,有一条sql没有执行成功,那么数据库可以根据undo log进行撤销,将所有修改过的数据从逻辑上恢复到修改之前的样子,注意是逻辑上还原成原来的样子。比如 insert了200条数据,那么就需要 delete这200条。所以说 undo log 是逻辑日志,与 redo log 记录的页操作物理日志不同。

(3)、log group
log group位重做日志组,一个重做日志组 (log group) 中有多个重做日志文件 (redo log file),当日志组中的第一个logfile被写满,则会开始将 redo log 写入日志组中的下一个重做日志文件中,以此类推,当日志组中的所有 redo log file 都被写满,则将redo log再写入到第一个redo log file中,覆盖原来的 redo log,以便新的redo log被写入。
如果重做日志所在的设备崩溃了,那么 redo log将有可能丢失,这样就无法保证 redo log 在任何时候都是可用的,所以,log group 还支持日志组镜像,为了保险起见,我们应该将 log group放在有冗余能力的设备上,比如 raid1。

mysql中,innodb存储引擎支持事务,myisam存储引擎是不支持事务的,不管是redo log还是 undo log,都是innodb的产物。而在mysql中还有另外一种重要的日志,二进制日志,也就是平常所说的 binlog,他是建立mysql主从复制环境时所必须的日志,但是binlog并不是innodb存储引擎层面的产物,而是整个mysql数据库层面的产物,mysql任何存储引擎对于数据库的更改都会产生二进制日志 (binlog) 。

innodb的 redo log 记录的是物理格式的日志,记录了对页的操作,而binlog记录的是逻辑日志,记录对应的sql
其实不管是redolog 还是 undo log 都可以理解成恢复数据库的手段。

二、事务日志参数

当使用innodb存储引擎时,我们可以通过如下语句查看日志配置参数:show global variables like '%innodb%log%';


事务日志参数.png

innodb_log_file_size: 表示redo log file 的大小,单位为字节,上图中的设置表示每个重做日志文件的大小为 5M
innodb_log_files_in_group :表示每个重做日志组中有几个 redo log file
innodb_log_group_home_dir:表示重做日志组文件所在路径。默认情况下为 /var/lib/mysql,windows系统下会在data目录下,在这个目录下有ib_logfile0与ib_logfile1即为日志组中两个重要日志,这两个日志文件大小为5M,对应了innodb_log_file_size的值。
innodb_mirrored_log_groups:表示一共有几个日志组。如果设置为1表示没有冗余的镜像日志组。注意,如果重做日志所在的硬件设备并没有冗余能力,同时用户对数据安全性比较高,那么往往需要将此值设置为大于等于2的值。
innodb_flush_log_at_trx_commit:表示当事务提交以后,是否立即将redo log 从内存 (log buffer) 刷写到redo log file 中。
如果此值设置为1 (默认值),表示事务提交时必须将 redo log 从 log buffer 中刷写到redologfile (磁盘)中,过程为:事务提交--log buffer---os buffer-- log file,此值为1时完全满足ACID的要求。
如果此值设置为0,事务提交时不会将 redo log 从 log buffer刷写到 redo log file,但是会在每秒钟自动刷写一次,也就是说如果mysql数据库崩溃,最多会丢失1秒的redo log。
如果此值设置为2,当事务提交时,redolog存在于 log buffer和os buffer中,每秒从 os buffer中刷写到log file中一次,这种情况下如果mysql宕机,操作系统没有宕机时,并不会丢失数据,所以可靠性要比设置为0时要高一些。
理论上来说:此值设置为1,安全性最高,性能最低。设置为0,性能最高,安全性最低。设置为2,性能较高,安全性较低。此值为1时,能够满足ACID特性,其他两个值则不满足ACID。
我们可以根据自己的需要,设置重做日志的相关参数。

1、事务控制语句
在mysql中,默认情况下我们每执行一条sql语句,mysql都会把这条sql当做一个单语句事务进行提交,并且是默认自动提交的。我们可以使用如下语句查看:
show global variables like '%autocommit%';
show session variables like '%autocommit%';


image.png

如上图所示,默认情况下,autocommit是开启的,表示事务都是自动提交的。
如果需要手动控制提交,需要显示的开启一个事务,或者禁用自动提交功能 (set autocommit=0),进行手动提交。

(1)、start transaction 或者 begin :表示显示的开启一个事务,在存储过程中不能用begin开启一个事务。
(2)、commit 或者 commit work:表示提交事务,也就是从begin到commit之间所有sql语句对数据库所做的修改会被真正执行
(3)、rollback 或者 rollback work:表示回滚事务,回滚事务会撤销所有未提交的修改并结束当前事务。注意,使用rollback回滚事务以后,当前事务会结束,后面的操作不算在当前事务以内。
(4)、savepoint 标识符:表示创建一个事务保存点,以便我们回滚到当前保存点,而不是回滚整个事务。
(5)、rollback to savepoint 标识符:表示根据标识符回滚到指定的保存点,使用rollback to savepoint只会撤销对应保存点之后的操作,并且不会结束当前事务,回滚到指定的保存点以后的操作仍然属于当前事务,于rollback不同。
(6)、release savepoint 标识符:表示删除一个保存点。

下面进行实际操作,演示一下效果:


image.png

先从最简单事务开始:


事务正常提交.png

下面演示事务回滚的例子:


事务正常回滚.png

事务开始之前,user表中有5条数据,事务开始以后我们删除id为 4和5的两条记录,但是我们并没有commit,所以这些操作没有真正持久化到数据库中,此时,我们执行了rollback,所有未提交的操作都被撤销了,同时当前事务结束。

上面演示了事务的正常提交和回滚,下面演示一下保存点。

事务保存点1.png

事务保存点2.png

通过事务控制语句,即可显示的手动的对事务进行控制,之前说过,我们可以禁用autocommit功能,从而进行手动的提交操作,示例如下:set @@session.autocommit=0;


关闭事务自动提交.png

因为关闭了自动提交功能,所以,每个sql语句并不会自动被当做一个单语句事务,所以每个sql语句并不会被自动提交。如果需要将之前的修改持久化,需要手动执行commit操作。

三、事务的隔离级别

1、事务隔离级别综述

mysql中,innodb所提供的事务复合ACID的要求,而通过事务日志中的 redo log和undo log 满足了原子性、一致性、持久性、事务还会通过锁机制满足隔离性,在innodb存储引擎中,有不同的隔离级别,他们有着不同的隔离性。
下面使用两个会话窗口演示隔离级别

image.png

image.png

此处,我们列出innodb中事务的所有隔离级别,然后逐个了解他们,事务隔离级别一共有如下四种:
1、READ-UNCOMMITTED:此隔离级别翻译为:“读未提交”
2、READ-COMMITTED:此隔离级别翻译为:“读已提交”
3、REPEATABLE-READ:此隔离级别翻译为:“可重复读”
4、SERIALIZABLE:此隔离级别翻译为:“串行化”

Mysql的默认隔离级别为REPEATABLE-READ,即 “可重复读”
查看mysql的隔离级别:show variables like 'tx_isolation';

image.png

如果需要修改配置文件 my.cnf配置文件,则可通过如下参数配置mysql的事务隔离级别
transaction_isolation=REPEATABLE-READ

下面详细介绍各个隔离界别

1、可重读

我们先来总结一下可重读隔离级别的特性。仍然以上面例子为例,在事务1中修改了一条数据以后,事务2看到的数据仍然是事务1修改之前的数据,即使事务1提交了,在事务2没有提交之前,事务2看到的数据还是相同的,所以这种隔离级别被称为“可重读”

但是你可能会有个问题,之前说过,事务的隔离性是由锁来实现的,那么,当事务1执行更新语句时,事务1应该对数据加了写锁,但是在事务2中,仍然可以进行查询操作,即进行读操作,可是写锁是排他锁,在事务1已经添加写锁的情况下,为什么事务2还可以读取呢?这是因为innodb采用了一致性非锁定读的机制提高了数据库并发性。一致性非锁定读表示如果当前行如果被施加了排他锁,那么当需要读取行数据时,则不会等待行上的锁的释放,而是会去读取一个快照数据。

一行记录可能有不止一个快照数据,并不是所有隔离级别都使用了一致性非锁定读,在“可重读”和“读提交”的隔离级别下,innodb存储引擎使用了一致性非锁定读,但是在这两个隔离级别中,对于快照的定义也不相同。在“可重读”隔离级别下,快照数据是当前事务开始的样子,但是在“读提交”的隔离级别下,由于快照定义不同,所以显示的现象也不同。

在可重读隔离级别下,可能会出现“幻读”的问题,那么什么是幻读,我们一起看一下


image.png

从上图可以看出,事务1commit之后,数据其实就已经发生了变化,但是事务2在没有update之前无法看到变化的数据,但是当事务2更新数据以后,发现莫名多出了一条数据。在同一个事务中,执行两次同样的sql,第二次sql返回之前不存在的行,或者之前出现的数据不见了,这种现象被称之为“幻读”。
注意:事务2中的update语句并没有指定任何条件,相当于更新所有行的对应字段,如果指定了条件,并没有更新到“隐藏”行,那么可能无法看到幻读现象
上述事务2还没有进行commit操作,如果不执行commit操作,那么所有数据都会回滚。也就是事务2还没有生效。

2、串行化

经过上述实例我们可以发现,事务处于“REPEATABLE-READ”可重读级别时,会出现幻读的情况,在之前我们提到,不同的隔离级别所引入的隔离性不同,那么有没有一种隔离级别能够解决幻读的问题呢?答案是:串行化可以解决这个问题。我们来试试串行化时,事务是怎么样工作的。

image.png

如上图,将两个会话窗口中的事务隔离级别设置为串行化后,分别开启两个事务,在事务1插入一条数据没有提交的情况下,此时事务2执行查询数据时好像是被卡主了,没有任何反应

过一段时间后,会报一个错:获取锁失败


image.png

换另外一种实验方式:在事务2被阻塞的时候,提交事务1,会发现事务2查询立即返回结果。

image.png

从上述实验来看,当事务处于串行化隔离级别时,是不可能出现幻读的情况的,因为如果另一个事务对表添加了写锁,那么在当前事务是无法读取到数据的,必须等到另一个事务提交或者回滚。使用串行化隔离级别不会出现幻读,但是数据库失去了并发的能力,所以我们很少将隔离级别设置为串行化,因为这种隔离性过于严格。

3、读已提交

现在我们已经了解了两种隔离级别,可重读与串行化。串行化隔离级别的隔离性是最强的,没有并发能力,可重读隔离级别的隔离性稍微次之,但是比较串行化而言,并发能力较好,不过存在“幻读”的问题。

image.png

同样在两个会话中开启两个事务,在事务1中修改数据,此时事务1还未提交,在事务2中并不能看到事务1中的修改,而当事务1提交以后,事务2中即可看到事务1中的修改,话句话说,就是事务2能够读到事务1提交后的更改,这种隔离级别被称为“读提交”。

在“读提交”的隔离级别下,也会出现“幻读”的问题,示例如下:

image.png

在上述示例中,事务1向表中插入了一行数据,在事务1提交以后,事务2中即可看到,但是事务2还没有提交,在事务2中执行两次相同的查询语句,莫名其妙的多出了一行,出现了“幻读”的情况。

在读已提交隔离级别下,除了会出现幻读的情况,还会出现不可重读的情况。“不可重读”表示“不一定可重读”,不要理解为“一定不可重读”。


image.png

上述例子中,事务1中修改了id=1的数据,提交以后,事务2中不能再读到之前的数据了,所以出现了“不可重读”的现象。
其实,总结一下这个隔离级别,是因为事务1的提交对事务2立即可见,所造成的“不可重读”和“幻读”的情况。

那么我们再来总结一下“读提交”这个隔离级别,在“读提交”隔离级别下,会出现“不可重读”,“幻读”的问题,比“可重读”隔离级别问题更多,但是并发能力更强。现在就剩下一个隔离级别没有了解到,那就是“读未提交”。

4、读未提交

还是以一个例子演示:


image.png

将隔离级别调整为“读未提交”,可以看出事务1并没有提交,但是事务2看到了事务1中锁做出的修改。
当事务能够看到别的事务中“未提交”的数据,我们称这种现象为“脏读”。
上例中,事务1并未提交,但是其所作出的修改已经能在事务2中查看到,由于事务1中的修改可能被回滚,或者继续被修改,所以事务2中看到的数据飘忽不定,并不是最终的数据,所以是“脏的”。在事务隔离级别为“读未提交”时,其并发性能最强,但是其隔离性与安全性是最差的。

因此,在事务处于“读未提交”这种隔离级别时,会出现“脏读”,同时也会出现“不可重读”,“幻读”的情况。

四、脏读、幻读、不可重读的区别

我们来总结一下它们之间的区别:
脏读:当前事务可以查看到其他事务未提交的数据(侧重点在于别的事务未提交)
幻读:幻读的表象于不可重读的表象都让人“懵逼”,很容易搞混,但是如果非要细分,幻读的侧重点在于新增和删除。表示在同一个事务中,使用相同的查询语句,第二次查询时,莫名的多出了一些之前不存在的数据,或者莫名的不见了一些数据。
不可重读:不可重读的侧重点在于更新数据。表示在同一事务中,查询相同的数据范围时,同一个数据资源莫名的改变了。

五、不同隔离级别所拥有的问题

下面做一个最终的总结:


image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容