第七章-事务

事务会把数据库从一种一致状态转换为另一种一致状态。在数据库提交工作时,可以确保要么所有修改都已经保存了,要么所有修改都不保存。

InnoDB存储引擎中的事务完全符合ACID的特性。ACID 是以下4个词的缩写:原子性(atomicity)、一致性(consistency)、隔离性(isolation)、持久性( durability)

本章主要关注事务的原子性这一概念,并说明怎样正确使用事务及编写正确的事务应用程序,避免在事务方面养成一些不好的习惯。

7.1认识事务285

7.1.1 概述 285

事务是访问并更新数据库中各种数据项的一个程序执行单元。在事务中的操作,要么都做修改,要么都不做,这就是事务的目的。

事务需要满足ACID特性,但是数据库厂商出于各种目的,并没有严格去满足事务的ACID标准。

原子性(Atomicity)

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

一致性(consistency)

C (consistency), 一致性。一致性指事务将数据库从一种状态转变为下一种一致的状态。在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。

隔离性( isolation)

事务的隔离性要求每个读写事务的对象对其他事务的操作对象能相互分离,即该事务提交前对其他事务都不可见,通常这使用锁来实现。

持久性(durability )

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

7.1.2 分类 287

从事务理论角度分:

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

扁平事务(Flat Transactions)

扁平事务是应用程序成为原子操作的基本组成模块。其实现简单,因此使用的最为频繁。

扁平事务的主要限制是不能提交或者回滚事务的某一部分,或分几个步骤提交,由此出现了带有保存点的扁平事务(Flat Transactions with Savepoints )

带有保存点的扁平事务(Flat Transactions with Savepoints )

除了支持扁平事务支持的操作外,允许在事务执行过程中回滚到同一事务中较早的一个状态。这是因为某些事务可能在执行过程中出现的错误并不会导致所有的操作都无效,放弃整个事务不合乎要求,开销也太大。保存点( Savepoint)用来通知系统应该记住事务当前的状态,以便当之后发生错误时,事务能回到保存点当时的状态。

链事务(Chained Transactions )

链事务(Chained Transaction)可视为保存点模式的一种变种。

链事务的思想是:在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式地传给下一个要开始的事务。注意,提交事务操作和开始下一个事务操作将合并为一个原子操作。

嵌套事务(Nested Transactions)

嵌套事务(Nested Transaction) 是一个层次结构框架。由一个顶层事务(top-level transaction)控制着各个层次的事务。顶层事务之下嵌套的事务被称为子事务(subtransaction),其控制每一个局部的变换。嵌套事务的层次结构如图7-4所示。

下面给出Moss对嵌套事务的定义:
1)嵌套事务是由若干事务组成的一棵树, 子树既可以是嵌套事务,也可以是扁平事务

2)处在叶节点的事务是扁平事务。但是每个子事务从根到叶节点的距离可以是不同的。

3)位于根节点的事务称为顶层事务,其他事务称为子事务。事务的前驱称(predecessor)为父事务(parent), 事务的下一层称为儿子事务( child)。

4)子事务既可以提交也可以回滚。但是它的提交操作并不马上生效,除非其父事务已经提交。因此可以推论出,任何子事物都在顶层事务提交后才真正的提交。

5)树中的任意一个事务的回滚会引起它的所有子事务一同回滚,故子事务仅保留A、C、I特性,不具有D的特性。

**注意:**可以用保存点技术来模拟嵌套事务

分布式事务(Distributed Transactions)

分布式事务(Distributed Transactions)通常是一个在分布式环境下运行的扁平事务,需要根据数据所在位置访问网络中的不同节点。分布式事务同样需要满足ACID特性。

7.2 事务的实现 294

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

7.2.1 redo 294

1.基本概念

重做日志用来实现事务的持久性,即事务ACID中的D。其由两部分组成:–是内存中的重做日志缓冲(redo log buffer),其是易失的;二是重做日志文件(redo log file),其是持久的。

为了确保每次日志都写入重做日志文件,在每次将重做日志缓冲写入重做日志文件后,InnoDB存储引擎都需要调用一次fsync操作。

InnoDB存储引擎允许用户手工设置非持久性的情况发生,以此提高数据库的性能。即当事务提交时,日志不写人重做日志文件,而是等待一个时间周期后再执行fsync操作,从而显著提高数据库的性能。

参数innodb_ flush_ log_ at trx_ commit 用来控制重做日志刷新到磁盘的策略。该参数的默认值为1,表示事务提交时必须调用一次fsync操作。0 表示事务提交时不进行写人重做日志操作。2表示事务提交时将重做日志写入重做日志文件,但仅写入文件系统的缓存中,不进行fsync 操作。

显然花费时间1>2>0

二进制日志(binlog)

在MySQL数据库中还有一种二进制日志(binlog),其用来进行POINT-IN-TIME(PIT)的恢复及主从复制(Replication)环境的建立。

二进制日志(binlog)与重做日志的区别:

  • 重做日志是在InnoDB存储引擎层产生,而二进制日志是在MySQL数据库
    的上层产生的。
  • 其次,两种日志记录的内容形式不同。MySQL数据库上层的二进制日志是一种逻辑日志,其记录的是对应的SQL语句。而InnoDB存储引擎层面的重做日志是物理格式日志,其记录的是对于每个页的改。
  • 此外,两种日志记录写人磁盘的时间点不同。二进制日志只在事务提交完成后进行一次写人。而InnoDB存储引擎的重做日志在事务进行中不断地被写人,这表现为日志并不是随事务提交的顺序进行写人的。
2.Log block

在InnoDB存储引擎中,重做日志都是以512字节进行存储的。这意味着重做日志缓存、重做日志文件都是以块(block) 的方式进行保存的,称之为重做日志块(redo log block),每块的大小为512字节。因此重做日志的写入可以保证原子性,不需要doublewrite技术。

重做日志块的结构

logbuffer是由logblock组成,在内部logbuffer就好似一个数组,因此

LOG_BLOCK_ HDR_ NO用来标记这个数组中的位置。其是递增并且循环使用的。

LOG_ BLOCK_ HDR_ DATA_ LEN 占用2字节,表示log block所占用的大小。

LOG_ BLOCK_ FIRST_ REC GROUP占用2个字节,表示log block中第一个日志所在的偏移量。如果该值的大小和LOG BLOCK_ HDR_ DATA_ _LEN相同,则表示当前log block不包含新的日志。

LOG_ BLOCK_ CHECKPOINT_ NO占用4字节,表示该log block最后被写人时的检查点第4字节的值。

log block tailer 只由1个部分组成(如表7-3所示),且其值和LOG_ BLOCK HDR_NO相同,并在函数log_ block init 中被初始化。

3.log group

log group为重做日志组,由多个重做日志文件组成。

InnoDB存储引擎运行过程中,log buffer根据一定的规则将内存中的log block刷新到磁盘。这个规则具体是:

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

log block对redolog fle的写入不是顺序的。因为redo log fle除了保存log buffer 刷新到磁盘
的log block,还保存了一些其他的信息,这些信息一共占用2KB大小。

对于log group中的第一个redo log file(只是第一个,其余的redo log file保留这些空间,但不保存上述信息),其前2KB的部分保存4个512字节大小的块,其中存放的内容如表7-4所示。

log group与redo log file之间的关系如下图所示:

4.重做日志格式

不同的数据库操作会有对应的重做日志格式。此外,由于InnoDB存储引擎的存储管理是基于页的,故其重做日志格式也是基于页的。虽然有着不同的重做日志格式,但是它们有着通用的头部格式。

前三个字符格式是相同的:redo_ log_ type: 重做日志的类型、space:表空间的ID、page_ no:页的偏移量。

redo log body的部分,根据重做日志类型的不同,会有不同的存储内容。

5.LSN

LSN是Log Sequence Number的缩写,其代表的是日志序列号。LSN记录的是重做日志的总
量,其单位为字节。

LSN不仅记录在重做日志中,还存在于每个页中。

Log sequence number表示当前的LSN, Log flushed up to表示刷新到重做日志文件
的LSN,Last checkpoint at表示刷新到磁盘的LSN。在生产环境中,这三个值可能是不同的。

6.恢复

InnoDB存储引擎在启动时不管上次数据库运行时是否正常关闭,都会尝试进行恢复操作。

因为重做日志记录的是物理日志,因此恢复的速度比逻辑日志,如二进制日志,要快很多。

由于checkpoint表示已经刷新到磁盘页上的LSN,因此在恢复过程中仅需恢复checkpoint开始的日志部分。

7.2.2 undo 305

1.基本概念

有时还需要进行回滚操作,这时就需要undo。因此在对数据库进行修改时,InnoDB存储引擎不但会产生redo,还会产生一定量的undo。

与redo不同,undo存放在数据库内部的一个特殊段(segment)中,这个段称为undo段(undo segment)。undo 段位于共享表空间内。

undo是逻辑日志,因此只是将数据库逻辑地恢复到原来的样子。所有修改都被逻辑地取消了,但是数据结构和页本身在回滚之后可能大不相同。

Undo执行的是相反的操作(例:对于每一个INSERT,InnoDB都会执行一个DELETE操作来进行undo),并不是直接恢复到事务开始前的状态。

undo log也会产生redo log,这是因为undo log也需要持久性的保护。

2.undo存储管理

InnoDB有rollback segment,每个回滚段记录了1024个undo log segment,而undo页的申请在undo log segment中进行。

事务提交时,会把undo log放入purge的列表中,留给后续的purge操作,并且判断undo log所在的页是否可以重用,若可以则分配给下个事务使用。

为什么要重用?因为为每一个事务分配一个单独的undo页会非常浪费存储空间。

如果判断重用?具体来说,当事务提交时,首先将undo log放人链表中,然后判断undo页的使用空间是否小于3/4,若是则表示该undo页可以被重用,之后新的undolog记录在当前undolog的后面。

3.undo log 格式

有2种:insert undo log和update undo log

insert undo log 是指在insert操作中产生的 undo log。因为insert操作对其他事务不可见,所以insert undo log在事务提交后,可以马上删除,不需要purge操作。

update undo log记录的是对delete和update操作产生的undo log。该undo log可能需要提供MVCC机制,因此不能在事务提交时就进行删除。提交时放入undo log链表,等待purge线程进行最后的删除。

4.查看undo信息

通过字典表INNODB_TRX_ROLLBACK_SEGMENTINNODB_TRX_UNDO可以查看undo相关信息

7.2.3 purge 317

purge用于完成最终的update和delete操作(例如:DELETE操作,逻辑删除一个记录是打上一个标记,最终的删除则由purge来处理。)。

对于按事务提交顺序挂在history list的undo log,purge会去history列表尾端找到最先提交的事务,然后找到对应的undo page,判断相应的undo log有没有事务引用,如果没有则清除,清除后,此页可以被重用。(书上有例子)这样设计的目的是:为了避免大量的随机读取操作,从而提高purge的效率。

history list的长度会影响性能,innodb_max_purge_lag用于控制history list的长度,innodb_purge_batch_size用于控制每次操作清理的undo page数量。

7.2.4 group commit 319

将多个事务的重做日志通过一次fsync刷新到磁盘,这样就大大地减少了磁盘的压力,从而提高了数据库的整体性能。对于写人或更新较为频繁的操作,group commit的效果尤为明显。

7.3 事务控制语句 323

  • START TRANSACTION | BEGIN:显式地开启一个事务。
  • COMMIT :要想使用这个语句的最简形式,只需发出COMMIT。也可以更详细一些,写为COMMIT WORK,不过这二者几乎是等价的。COMMIT会提交事务,并使得已对数据库做的所有修改成为永久性的。
  • ROLLBACK :要想使用这个语句的最简形式,只需发出ROLLBACK。同样地,也可以写为ROLLBACKWORK,但是二者几乎是等价的。回滚会结束用户的事务,并撤销正在进行的所有未提交的修改。
  • SAVEPOINT identifier : SAVEPOINT 允许在事务中创建一个保存点,一个事务中可以有多个SAVEPOINT.
  • RELEASE SAVEPOINT identifier:删除一 个事务的保存点,当没有一个保存点执行这句语句时,会抛出一个异常。
  • ROLLBACK TO[ SAVEPOINT]identifier:这个语句与SAVEPOINT命令一起使用。可以把事务回滚到标记点,而不回滚在此标记点之前的任何工作。例如可以发出两条UPDATE语句,后面跟一个SAVEPOINT,然后又是两条DELETE语句。如果执行DELETE语句期间出现了某种异常情况,并且捕获到这个异常,同时发出了ROLLBACK TO SAVEPOINT命令,事务就会回滚到指定的SAVEPOINT,撤
    销DELETE完成的所有工作,而UPDATE语句完成的工作不受影响。
  • SETTRANSACTION:这个语句用来设置事务的隔离级别。InnoDB存储引擎提供的事务隔离级别有: READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE.

注意:ROLLBACK TO SAVEPOINT命令并不真正地结束事务。这时事务仍需要用户显式地运行COMMIT或ROLLBACK命令来进行提交或者回滚。

7.4 隐式提交的SQL语句 328

以下语句执行后,默认执行COMMIT操作:

  • DDL 语句: ALTER D ATAB ASE…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 , RENAME TA BLE, 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操作,所以无法回滚。

7.5 对于事务操作的统计 329

如果用户的程序都是显式控制事务的提交回滚,那么可以通过com_ commit和com_ rollback进行统计。如果不是,那么情况就有些复杂了。

7.6 事务的隔离级别 330

SQL标准定义的四个隔离级别为:

  • READ UNCOMMITTED 非提交读:会产生脏读、不可重复读和幻读。
  • READ COMMITTED 提交读:解决了脏读,但会出现不可重复读和幻读。
  • REPEATABLE READ 可重复读:解决了脏读和不可重复读,但会出现幻读。
  • SERIALIZABLE 序列化:解决所有问题,但是效率低

注意:在RR级别下,其他数据库会产生幻读,但INNODB不会,因为INNODB使用了Next-Key Lock的算法,从而避免了幻读。所以InnoDB存储引擎在REPEATABLE READ隔离级别下就可以达到3°的隔离,因此一般不在本地事务中使用SERIALIABLE的隔离级别。SERIALIABLE 的事务隔离级别主要用于InnoDB存储引擎的分布式事务。

7.7 分布式事务 335

7.7.1 MySQL数据库分布式事务 335

InnoDB存储引擎提供了对XA(eXtended Architecture)事务的支持,并通过XA事务来支持分布式事务的实现。

分布式事务指的是允许多个独立的事务资源( transactional resources)参与到一个全局的事务中。

全局事务要求在其中的所有参与的事务要么都提交,要么都回滚,这对于事务原有的ACID要求又有了提高。

另外,在使用分布式事务时,InnoDB 存储引擎的事务隔离级别必须设置为SERIALIZABLE。

XA事务由一个或多个资源管理器(ResourceManagers)、一个事务管理器(Transaction Manager)以及一个应用程序(Application Program)组成。

  • 资源管理器:提供访问事务资源的方法。通常-一个数据库就是一-个资源管理器。

  • 事务管理器:协调参与全局事务中的各个事务。需要和参与全局事务的所有资源管理器进行通信。

  • 应用程序:定义事务的边界,指定全局事务中的操作。

  • 在MySQL数据库的分布式事务中,资源管理器就是MySQL数据库,事务管理器为连接MySQL服务器的客户端。

图7-22显示了一个分布式事务的模型。

分布式事务使用两段式提交(two-phase commit)的方式。

在第一阶段,所有事务都准备好提交;在第二阶段,事务管理器告诉资源管理器执行ROLLBACK还是COMMIT。

7.7.2 内部XA事务 340

MySQL数据库中还存在另外一种分布式事务,其在存储引擎与插件之间,又或者在存储引擎与存储引擎之间,称之为内部XA事务。

最为常见的内部XA事务存在于binlog与InnoDB存储引擎之间。

MySQL数据库在binlog与InnoDB存储引擎之间采用XA事务,来避免主从不一致的问题。

7.8 不好的事务习惯 341

7.8.1 在循环中提交 341

因为InnoDB存储引擎默认是自动提交的,且每次提交都要写一次重做日志。所以尽量不要在循环中反复提交操作,而应该把整个循环作为一个事务,从而只写一次重做日志,大大提高效率。

7.8.2 使用自动提交 343

默认设置使用自动提交( autocommit),可以使用如下语句来改变当前自动提交的方式:

 SET autocommit=0;

也可以使用START TRANSACTION, BEGIN 来显式地开启一个事务。在显式开启事务后,在默认设置下(即参数completion_type等于0),MySQL会自动地执行SET AUTOCOMMIT=0的命令,并在COMMIT或ROLLBACK结束一个事务后执行SET AUTOCOMMIT= 1。

7.8.3 使用自动回滚 344

在存储过程中,使用自动回滚,会出现这样的问题:如果存储过程发生回滚,却不知道是存储过程中的那个地方出现了错误。

解决方法:

在存储过程中去掉对事务的控制语句,将这些操作都交由程序来完成,也就是说让程序来控制事务。

7.9 长事务 347

长事务,就是执行时间比较长的事务。

如果在长事务中发生错误,回滚的代价非常大。因此可以将长事务转化为小批量(minibatch)的事务来进行处理。

这样一次回滚的代价就很小,且用户还可以知道现在总的事务大概已经执行到了哪个阶段。

7.10 小结 349

几点总结:

  • 事务必须遵循ACID特性,即Atomicity(原子性)、Consistency(–致性)、Isolation(隔离性)和Durability(持久性)。隔离性通过第6章介绍过的锁来完成;原子性、一致性、隔离性通过redo和undo来完成。
  • InnoDB存储引擎的默认事务隔离级别是REPEATABLE READ的,不同于SQL标准对于事务隔离级别的要求,InnoDB存储引擎在REPEATABLE READ隔离级别下就可以达到3°的隔离要求。
  • 在默认配置下,MySQL数据库总是自动提交的。
  • 此外,在应用程序中,最好的做法是把事务的START TRANSACTION、COMMIT、ROLLBACK操作交给程序端来完成,而不是在存储过程内完成。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值