第七章 事务(上)

7.1 认识事务
7.1.1 概述
事务可由一条非常简单的SQL语句组成,也可以由一组复杂的SQL语句组成。事务是访问并更新数据库中各种数据项的一个程序执行单元。在事务中的操作,要么都做修改,要么都不做,这就是事务的目的,也是事务模型区别与文件系统的重要区别之一。理论上说,事务有着极其严格的定义,它必须同时满足四个特性,即通常所说的事务的ACID特性。但是数据库厂商处于各种目的,并没有严格去满足事务的ACID标准。对于InnoDB存储引擎而言,其默认的事务隔离级别为READ REPEATABLE ,完全遵循和满足事务的ACID特性。

A(Atomicity),原子性。原子性是指整个数据库事务是一个不可分割的工作单位。只有事务中所有的数据库操作都执行成功,才算整个事务成功。事务中任何一个SQL语句执行失败,已经执行成功的SQL语句也必须撤销,数据库状态应该退回到执行事务前的状态。

C(Consistency),一致性。一致性是指事务从一种状态转变为下一种一致的状态,在事务开始之前和事务结束以后,数据库完整性约束没有被破坏。例如,在表中有一个字段为姓名,为唯一性约束。如果有一个事务对姓名字段进行了修改,但是在事务提交或事务发生回滚之后,表中姓名字段变得非唯一了,这就破坏了事务的一致性要求,即事务将数据库从一种状态变成了一种不一致的状态。

D(Durability),持久性。事务一旦提交,其结果就是永久性的。即使发生宕机等故障,数据库也能将数据恢复。持久性保证事务系统的高可靠性(High Reliability),而不是高可用性(High Availablity)。

7.1.2 分类
从事务理论的角度来说,可用把事务分为以下几种类型:

  • 扁平事务(Flat Transactions)
  • 带有保存点的扁平事务(Flat Transactions with Savepoints)
  • 链事务(Chained Transactionss)
  • 嵌套事务(Nested Transactions)
  • 分布式事务(Distributed Transactions)

扁平事务(Flat Transaction)
事务类型中最简单也是使用最为频繁的事务。在扁平事务中,所有操作都处于同一层次,其有BEGIN WORK开始,有COMMIT WORK或ROLLBACK WORK结束,其间的操作都是原子的,要么都执行,要么都回滚。扁平事务的主要限制是不能提交或者回滚事务的某一部分,或分几个步骤提交。如果要支持有计划的回滚操作,那么就不需要终止整个事务。因此就出现了带有保存点的扁平事务

带有保存点的扁平事务
除了支持扁平事务外,允许事务在执行过程中回滚到同一个事务中较早的一个状态。保存点用来通知系统应该记住事务当前的状态,以便当之后发生错误时,事务能回到保存点当时的状态。保存点用SAVE WORK函数来建立,通知系统记录当前的处理状态。保存带你在事务内部是递增的,这意味着ROLLBACK WORK不影响保存点的计数,并且单调递增的编号能保持事务执行的整个历史过程,包括在执行过程中想法的改变。

链事务
可视为保存点模式的一种变种。带有保存点的扁平事务,当系统发生崩溃是,所有保存点都将消失,因为其保存点是易失的,而非持久的。这意味着当进行恢复是,事务需要从开始处重新执行,而不能从最近的一个保存点继续执行。
链事务的思想是:在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式地传给下一个要开始的事务。注意,提交事务操作和开始下一个事务操作将合并为一个原子操作。这意味着下一个事务能看到上一个事务的结果,就好像在一个事务中进行一样。
链事务和带有保存点的扁平事务不同的是,带有保存点的扁平事务能回滚到任意正确的保存点。而链事务的回滚仅限于当前事务,即只能恢复到最近一个保存点。对于锁的处理,两者也不同。链事务在执行commit之后即释放了当前事务所持有的锁,而带有保存点的扁平事务不影响迄今为止所持有的锁。

嵌套事务
有一个顶层事务控制着各个层次的事务。顶层事务之下嵌套的事务被称为子事务,其控制每一个局部的变换。
1.嵌套事务是由若干事务组成的一棵树,子树既可以是嵌套事务,也可以是扁平事务
2.处在叶节点的事务是扁平事务。但是每个子事务从根到叶节点的距离可以是不同的。
3.位于根节点的事务称为顶层事务,其他事务称为子事务。事务的前驱称为父事务,事务的下一层称为儿子事务。
4.子事务既可以提交,也可以回滚。但是它的提交并不马上生效,除非其父事务已经提交。因此可以推出,任何子事务都在顶层事务提交之后才能真正的提交。
5.树中的任意一个事务的回滚会引起它的所有子事务一同回滚,故子事务仅保留A,C,I 特性,不具有D的特性

分布式事务
通常是在一个分布式环境下运行的扁平事务,因此需要根据数据所在位置访问网络中的不同节点。

7.2 事务的实现
事务的隔离性由锁来实现。原子性、一致性、持久性通过数据库的redo log和undo log来完成。redo log称为重做日志,用来保证事务的原子性和持久性。undo log用来保证事务的一致性。

7.2.1 redo

1.基本概念
重做日志用来实现事务的持久性。其右两部分组成:一是内存中的重做日志缓冲(redo log buffer),其是易失的;二是重做日志文件(redo log file),其是持久的。

InnoDB是事务的存储引擎,其通过Force Log at Commit机制实现事务的持久性,即当事务提交时,必须先将该事务的所有日志写入到重做日志文件进行持久化,待事务的commit操作完成才算完成。这里的日志是指重做日志,在InnoDB存储引擎中,由两部分组成,即redo log和undo log。redo log用来保证事务的持久性,undo log用来帮助事务回滚即MVCC功能。redo log 基本上都是顺序写的,在数据库运行时不需要对redo log的文件进行读取操作。而undo log是需要进行随机读取的。
为了确保每次日志都写入重做日志文件,在每次将重做日志缓冲写入重做日志文件后,InnoDB存储引擎都需要调用一次fsync操作。这是因为重做日志文件打开并没有使用O_DIRECT选项,因此重做日志缓冲先写入文件系统缓存。为了确保重做日志写入磁盘,必须进行一个fsync操作,由于fsync的效率取决于磁盘的性能,所以磁盘的性能决定了事务提交的性能,也就是数据库的性能。

参数innodb_flush_log_at_trx_commit用来控制日志刷新到磁盘的策略。
当该参数值为0时,表示事务提交时不进行写入重做日志的操作,这个操作仅在Master Thread中完成,而在Master Thread中每1秒会进行一次重做日志文件的fsync操作。
当该参数值为1(默认)时,表示事务提交时必须调用一次fsync操作。
当该参数值为2时,表示事务提交时将重做日志写入重做日志文件,但仅写入文件系统的缓存中,不进行fsync操作。在这个设置下,当MySQL数据库发生宕机而操作系统不发生宕机时,并不会导致事务的丢失。而当操作系统宕机时,重启数据库后会丢失文件系统缓存刷新到重做日志文件那部分事务。
虽然用户可以通过设置参数innodb_flush_log_at_trx_commit为0或者2来提高事务提交的性能,但是需要牢记的是,这种设置方法丧失了事务的ACID特性。

在MySQL数据库中还有一种二进制日志(bin log),其用来进行point-in-time的恢复以及主从复制(Replication)环境的建立。

重做日志和二进制日志的区别是:
首先,重做日志是在InnoDB存储引擎层产生,而二进制日志是在MySQL数据库上层产生的,并且二进制日志不仅仅针对于InnoDB存储引擎,MySQL数据库中任何存储引擎对于数据库的更改都会产生二进制日志。
其次,两种日志记录的内容形式不同。MySQL数据库上层的二进制日志是一种逻辑日志,其记录的是对应SQL语句。而InnoDB存储引擎层面的重做日志是物理格式日志,其记录的是对于每个页的修改。
此外,两种日志记录写入磁盘的时间点不同,如图7-6所示。二进制日志只在事务提交完成后进行一次写入。而InnoDB存储引擎的重做日志在事务进行中不断地被写入,这表现为日志并不是随事务提交的顺序进行写入的

2. log block
在InnoDB存储引擎中,重做日志都是以512字节进行存储的。这意味着重做日志缓冲,重做日志文件都是以块(block)的方式进行保存的,称之为重做日志块(redo log block),每块都是512字节。
若一个页中产生的重做日志数量大于512字节,那么需要分隔为多个重做日志块进行存储。此外,由于重做日志块的大小和磁盘扇区块大小一样,都是512字节,因此重做日志的写入可以保证原子性,不需要double write技术。

3.log group
log group为重做日志租,其中有多个重做日志文件。log group是一个逻辑上的概念,并没有一个实际存储的物理文件来表示log group 信息。log group由多个重做日志文件组成,每个log group中日志文件的大小是相同的。重做日志文件中存储的就是之前在log buffer中保存的log block,因此其也是根据块的方式进行物理存储的管理,每个块的大小与log block一样,同样为512字节。在InnoDB存储引擎运行过程中,log buffer根据一定的规则将内存中log block刷新到磁盘。这个规则就是:

  • 事务提交时
  • 当log buffer中有一半的内存空间已经被使用时
  • log checkpoint 时

5. LSN
LSN是Log Sequence Number的缩写,其代表的是日志序列号。在InnoDB存储引擎中,LSN占用8字节,并且单调递增。LSN表示的含义有:

  • 重做日志写入的总量
  • checkpoint的位置
  • 页的版本

LSN表示事务写入重做日志的字节的总量。LSN不仅记录在重做日志中,还存在每个页中。在每个页的头部,有一个值FIL_PAGE_LSN,记录了该页的LSN,在页中,LSN表示该页最后刷新时LSN的大小,因为重做日志记录的是每个页的日志,因此页中的LSN用来判断页是否需要进行恢复操作。例如,页P1的LSN为1000,而数据库启动时,InnoDB检测到写入重做日志中的LSN为1300,并且该事务已经提交,那么数据库需要进行恢复操作,将重做日志应用到P1页中。同样的,对于重做日志中LSN小于P1页的LSN,不需要进行重做,因为P1页的LSN表示页已经刷新到该位置。

6. 恢复
InnoDB存储引擎在启动时不管上次数据库运行时是否正常关闭,都会尝试进行恢复操作。因为重做日志记录的是物理日志,因此恢复的速度比逻辑日志要快得多。由于checkpoint表示已经刷新到磁盘上的LSN,因此在恢复过程中仅需恢复checkpoint开始的日志部分。

7.2.2 undo

1.基本概念
事务有时候需要进行回滚操作,这是就需要undo。因此在对数据库进行修改时,InnoDB存储引擎不但会产生redo,还会产生一定量的undo。这样如果用户执行的事务或者语句由于某种因为失败了,又或者用一条rollback语句请求回滚,就可以利用这些undo信息将数据回滚到修改之前的样子。
redo存放在重做日志文件中,与redo不同,undo存放在数据库内部的一个特殊段(segement)中,这个段称为undo段(undo segement)。undo段位于共享表空间内。undo是逻辑日志,因此只是将数据库逻辑地恢复到原来的样子。所有修改都被逻辑地取消了,但是数据结构和页本身在回滚之后可能大不相同。
InnoDB存储引擎回滚时,它实际上做的是与先前相反的工作。对于每个insert,innodb存储引擎会执行一个delete;对于每一个delete,innodb存储引擎会执行一个insert;对于每一个update,innodb存储引擎会执行一个相反的update,将修改前的行放回去。
除了回滚操作,undo的另一个作用是MVCC,即在InnoDB存储引擎中MVCC的实现是通过undo来完成的。当用户读取一行记录时,若该行记录已经被其他事务占用了,当前事务可以通过undo读取之前的行版本信息,以此来实现非锁定读。
undo log会产生redo log,也就是undo log的产生会伴随redo log的产生,这是因为undo log也需要持久性的保护。

2. undo存储管理
InnoDB存储引擎对undo的管理同样采用段的方式。但是这个段和之前介绍的端有所不同。首先InnoDB存储引擎有rollback segement,每个回滚段中记录了1024个undo log segement,而在每个undo log segement段中进行段的申请。
事务在undo log segement分配页并写入undo log的这个过程同样需要写入重做日志。当事务提交时,InnoDB存储引擎会做以下两件事情:

  • 将undo log放入列表中,以供之后的purge操作
  • 判断undo log所在的页是否可以重用,若可以分配给下个事务使用。

事务提交后并不能马上删除undo log及undo log所在的页。这是因为可能还有其他事务需要通过undo log来得到行记录之前的版本。故事务提交时将undo log放入一个链表中,是否可以最终删除undo log及undo log所在页有purge线程来判断。
判断undo log所在页是否可以重用的标准是判断undo页使用的空间是否小于4/3 ,若是则表示该undo 页可以被重用,之后新的undo log记录在当前undo log的后面。

3. undo log格式
在InnoDB存储引擎中,undo log分为:

  • insert undo log
  • update undo log

insert undo log是指在insert操作中产生的undo log,因为insert操作的记录,只对事务本身可见,对其他事务不可见(事务隔离性的要求),故该undo log可以在事务提交后直接删除。不需要进行purge操作。
update undo log记录的是对delete和update操作产生的undo log。该undo log可能需要提供MVCC机制,因此不能在事务提交时就进行删除。提交时放入undo log链表,等待purge线程进行最后的删除。

delete操作并不直接删除记录,而只是将记录标记为已删除,也就是将记录的delete flag 设置为 1 。而记录最终的删除是在purge操作中完成的。
update主键的操作其实分两步完成。首先将原主键记录标为已删除,之后再插入一条新的记录。

7.2.3 purge
delete和update操作可能并不直接删除原有的数据。例如对于delete from t where a = 1;,表 t 上列 a 有聚集索引,列 b 上有辅助索引。对于上述delete操作,仅是将主键列等于1的记录delete flag 设置为 1,记录并没有被删除,即记录还是存在于B+树中。其次,对于辅助索引上 a 等于 1,b等于 1 的记录同样没有做任何处理,甚至没有产生undo log。而真正删除这行记录的操作其实被 "延时" 了,最终在purge操作中完成。

**7.2.4 group commit **
为了提供 fsync 的效率,当前数据库都提供了 group commit 的功能,即一次fsync可以刷新确保多个事务日志被写入文件。对于InnoDB存储引擎来说,事务提交时会进行两个阶段的操作:
1)修改内存中事务对应的信息,并且将日志写入重做日志缓冲。
2)调用fsync将确保日志都从重做日志缓冲写入磁盘。
步骤 2)相对步骤 1)是一个较慢的过程,这是因为存储引擎需要与磁盘打交道。当有事务进行这个过程时,其他事务可以进行步骤 1)的操作,正在提交的事务完成提交操作后,再次进行步骤 2),可以将多个事务的重做日志通过一次fsync刷新到磁盘,这样就大大地减少了磁盘的压力,从而提高了数据库的整体性能。对于写入或者更新较为频繁的操作,group commit的效果尤为明显。

7.3 事务控制语句
在MySQL命令行的默认设置下,事务都是自动提交(auto commit)的,即执行SQL语句后就会马上执行commit操作。因此要显示地开启一个事务需要使用命令BEGIN,START TRANSACTION,或者执行命令 SET AUTOCOMMIT = 0,禁用当前会话的自动提交。
事务控制语句:

  • start transaction|begin:显式的开启一个事务。
  • commit:提交事务
  • rollback:回滚事务,并撤销正在进行的所有未提交的修改。
  • savepoint indentifier:在事务中创建一个保存点
  • release savepoint identifier:删除一个事务的保存点
  • rollback to identifier:回滚到某个保存点
  • set transaction:用来设置事务的隔离级别

在存储过程总只能使用 start transaction 语句来开启一个事务
commit和commit work基于基本上是一致的,都是用来提交事务。不同之处在于commit work用来控制事务结束之后的行为是chain还是release的。如果是chain方式,那么事务就变成了链事务。
可以通过参数completion_type来进行控制,该参数默认为0,表示没有任何操作,在这种设置下commit和commit work是完全等价的。当该参数值为1时,commit work等同于commit and chain,表示马上自动开启一个相同隔离级别的事务。当该参数值为2时,commit work等同于commit and release。在事务提交之后会自动断开与服务器的连接。

** 7.4 隐式提交的SQL语句 **
以下这些SQL语句会产生一个隐式的提交操作,即执行完这些语句之后,会有一个隐式的commit操作。
DDL语句:alter database...upgrade data directory name,alter event,alter procedure,alter table,alter view,create database,create event,create index,create procedure,create table,create trigger,create view,drop database,drop event,drop index,drop procedure,drop table,drop trigger,drop view,pename table,truncate table。

用来隐式地修改MySQL架构的操作:create user,drop user,grant,rename user,revoke,set password

管理语句:analyze table,cache index,check table,load index into cache,optimize table,repair table。

此外,truncate table语句是DDL语句,虽然和对整张表执行delete的结果是一样的,但它是不能回滚的。

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

推荐阅读更多精彩内容