MySQL事务基础

前言

在关系型数据库中,事务的重要性不言而喻,它是区别于文件系统的重要特征之一,在事务中涉及到的事务特性,隔离级别以及实现等,都是需要我们去理解以及深入探索的,只有理解了其运行和实现方式,才能在生产过程中运用灵活。在这篇文章中,主要介绍下事务的整体的知识框架和基本内容的介绍,可能涉及不深或不全面,但没关系,尝试着去理解,你也能收获不少。

(注:本篇文章中主要以InnoDB存储引擎为例)

事务的运用在生活中很常见,举个很经典的例子,例如银行中的转账应用,A用户需要转1000元给B用户,那么必须要经过的的步骤有:

1、检查A用户的账户余额是否高于1000元

2、从A账户中减去1000元

3、B账户中增加1000元

上面的整个流程我们将它归纳为一个原子性的操作,应该将他们打包在同一个事务中,其中任何一个操作失败都应该都应该回滚每一个操作流程。

所以我们先来看下事务是什么,它是如何定义的。

事务定义

事务(Transaction)可以由一条非常简单的SQL语句组成,也可以由一组复杂的SQL语句组成,事务是访问并更新数据库中各种数据项的一个程序执行单元,是一组原子性的SQL查询,或者说是独立的工作单元。在事务内的语句操作,要么全部执行成功,要么全部执行失败,这是事务的目的也是事务模型区别于文件系统的重要特征之一。

简单的来说,事务是引擎层实现,是访问并更新数据库中各个数据项的一个程序执行单元,事务内的语句操作,要么全部成功,要么全部失败。

开启事务的方式

那么怎样开启一个事务?

(1)通过语句显示启动语句 START TRANSACTION 开启一个事务,使用 COMMIT 提交事务将修改的数据持久保存,当执行过程中发生错误时可使用 ROLLBACK 撤销所有的修改。

(2)通过命令 set autocommit=0事务中默认是自动提交,可以通过设置 set autocommit=0,将线程的自动提交关闭,当执行一个SQL语句时,事务就开启了,同样使用 commit或者 rollback 语句,提交或回滚操作。

事务分类

从理论的角度可以把事务分为:

❑扁平事务(Flat Transactions)

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

❑链事务(Chained Transactions)

❑嵌套事务(Nested Transactions)

❑分布式事务(Distributed Transactions)

扁平事务(Flat Transactions)

事务中最简单的一种,在生产环境中使用最频繁。由COMMIT WORK或ROLLBACK WORK结束,其中的操作是原子的,要么都执行,要么都回滚。因此扁平事务是应用程序成为原子操作的基本组成模块。

扁平事务的最主要的限制是不能提交或者回滚事务的某一部分,或者分几个步骤提交。

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

带有保存点的扁平事务,除了支持支持扁平事务支持的操作外,还允许事务在执行过程中回滚到同一事务中较早的一个状态。保存点(Savepoint)用来通知系统应该记住事务当前的状态,以便当之后发生错误时,事务能回到保存点当时的状态。

下图是在事务中使用保存点的例子:

Image

注:保存点在事务内部是递增的。

链事务(Chained Transactions)

链事务是保存点模式的一种变种。保存点是易失的且是非持久的,当系统发生崩溃时所有的保存点将消失,这就意味着恢复时需要从事务的开始处重新执行,而不能从最近的一个保存点继续执行。

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

链事务与带有保存点的扁平事务不同的是,带有保存点的扁平事务能回滚到任意正确的保存点。而链事务中的回滚仅限于当前事务,即只能恢复到最近一个的保存点。

嵌套事务(Nested Transactions)

嵌套事务是一个层次结构框架,由一个顶层事务(top-level transaction)控制着各个层次的事务。

顶层事务之下嵌套的事务被称为子事务(subtransaction),其控制每一个局部的变换。

如下图为嵌套事务的层次结构图:

image-20210628231113059

下面给出Moss对嵌套事务的定义:

1)嵌套事务是由若干事务组成的一棵树,子树既可以是嵌套事务,也可以是扁平事务。

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

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

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

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

在Moss的理论中,实际的工作是交由叶子节点来完成的,即只有叶子节点的事务才能访问数据库、发送消息、获取其他类型的资源。而高层的事务仅负责逻辑控制,决定何时调用相关的子事务。

InnoDB存储引擎支持扁平事务、带有保存点的事务、链事务、分布式事务。对于嵌套事务,其并不原生支持。

分布式事务(Distributed Transactions)

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

事务控制语句

❑START TRANSACTION | BEGIN:显式地开启一个事务。

❑COMMIT:要想使用这个语句的最简形式,只需发出COMMIT。也可以更详细一些,写为COMMIT WORK,不过这二者几乎是等价的。COMMIT会提交事务,并使得已对数据库做的所有修改成为永久性的。

❑ROLLBACK:要想使用这个语句的最简形式,只需发出ROLLBACK。同样地,也可以写为ROLLBACK WORK,但是二者几乎是等价的。回滚会结束用户的事务,并撤销正在进行的所有未提交的修改。

❑SAVEPOINT identifier∶SAVEPOINT允许在事务中创建一个保存点,一个事务中可以有多个SAVEPOINT。

❑RELEASE SAVEPOINT identifier:删除一个事务的保存点,当没有一个保存点执行这句语句时,会抛出一个异常。

❑ROLLBACK TO[SAVEPOINT]identifier:这个语句与SAVEPOINT命令一起使用。可以把事务回滚到标记点,而不回滚在此标记点之前的任何工作。注意,ROLLBACK TOSAVEPOINT命令并不真正地结束事务。

例如可以发出两条UPDATE语句,后面跟一个SAVEPOINT,然后又是两条DELETE语句。如果执行DELETE语句期间出现了某种异常情况,并且捕获到这个异常,同时发出了ROLLBACK TO SAVEPOINT命令,事务就会回滚到指定的SAVEPOINT,撤销DELETE完成的所有工作,而UPDATE语句完成的工作不受影响。

❑SET TRANSACTION:这个语句用来设置事务的隔离级别。

InnoDB存储引擎提供的事务隔离级别有:READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE。

❑COMMIT和COMMIT WORK语句基本是一致的,都是用来提交事务。不同之处在于COMMIT WORK用来控制事务结束后的行为是CHAIN还是RELEASE的。如果是CHAIN方式,那么事务就变成了链事务。

通过参数completion_type来进行控制:

值为0时,默认值,表示没有任何操作,COMMIT和COMMIT WORK是完全等价的。

值为1时,COMMIT WORK等同于COMMIT AND CHAIN,表示马上自动开启一个相同隔离级别的事务。

值为2时,COMMIT WORK等同于COMMIT ANDRELEASE。在事务提交后会自动断开与服务器的连接。

注意点和使用建议

注意点:隐式提交的SQL语句是不能回滚的,即不能通过rollback去回滚,但若因某种原因失败了,系统会自动将其回滚,原因是隐式提交的 SQL 语句在执行前后会自动提交,是隐式的。

以下是一些隐式提交的 SQL 语句:

❑ DDL语句:ALTER DATABASE…UPGRADE DATA DIRECTORY NAME,ALTEREVENT,ALTER PROCEDURE,ALTER TABLE,ALTER VIEW,CREATEDATABASE,CREATE EVENT,CREATE INDEX,CREATE PROCEDURE,CREATE TABLE,CREATE TRIGGER,CREATE VIEW,DROP DATABASE,DROP EVENT,DROP INDEX,DROP PROCEDURE,DROP TABLE,DROPTRIGGER,DROP VIEW,RENAME TABLE,TRUNCATE TABLE。

❑用来隐式地修改MySQL架构的操作:CREATE USER、DROP USER、GRANT、RENAME USER、REVOKE、SET PASSWORD。

❑管理语句:ANALYZE TABLE、CACHE INDEX、CHECK TABLE、LOAD INDEXINTO CACHE、OPTIMIZE TABLE、REPAIR TABLE。

另外,TRUNCATE TABLE语句是DDL,因此虽然和对整张表执行DELETE的结果是一样的,但它是不能被回滚的。

使用建议:

1、不应该在一个循环中反复进行提交操作,不论是显式的提交还是隐式的提交。

2、最好把事务的控制权限交给开发人员,即在程序端进行事务的开始和结束,在程序中控制事务的好处是,用户可以得知发生错误的原因。

3、通过批量处理小事务来完成大事务的逻辑。

4、建议尽量不要使用长事务,长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间。

事务的特性(ACID)

ACID表示原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。

原子性(atomicity)

整个事务是不可分割的工作单位,事务中的所有操作要么全部提交成功,要么全部失败回滚,不可能出现部分成功的情况。

一致性(consistency)

一致性是指事务将数据库中的数据从一种状态转变为另一种状态,数据库的完整性约束没有被破坏,在事务执行前后都是合法的数据状态。

其中,数据库的完整性约束包括但不限于:实体完整性(如行的 主键存在且唯一),列完整性(如字段类型大小要符合要求)、外键约束,用户自定义完整性(如转账前后,两个账户的余额和应该不变)

例如上例中的转账案例,如果在转账过程中发生系统崩溃,A账户并不会损失1000元,因为事务未被提交,它所做的修改也不会保存到数据库中。

再例如,在表中有一个字段为姓名,为唯一约束,即在表中姓名不能重复。如果一个事务对姓名字段进行了修改,但是在事务提交或事务操作发生回滚后,表中的姓名变得非唯一了,这就破坏了事务的一致性要求,即事务将数据库从一种状态变为了一种不一致的状态。因此,事务是一致性的单位,如果事务中某个动作失败了,系统可以自动撤销事务——返回初始化的状态。

隔离性(isolation)

通常来说,事务提交前,其内部的操作与其他事务是相互隔离的,不可见的,针对的是不同事务。

隔离性还有其他称呼如并发控制(concurrency control)、可串行化(serializability)、锁(locking)。

持久性(durability)

持久性是指事务一旦提交,其所做的修改就会永久保存在数据库中,即使发生宕机等故障,数据库也能将数据恢复。当然这些故障指的是数据库本身的故障,并不指一些外部原因。

事务的隔离级别

事务之间必有隔离性,实现隔离性的最简单方式就是允许事务并发,当数据库当中有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了“隔离级别”的概念。

脏读(dirty read):脏读指的就是在不同的事务下,当前事务可以读到另外事务未提交的数据,简单来说就是可以读到脏数据。

image-20210628231232529

在会话A中的事务未提交的前提下,会话B中2次读取到的结果不一致,即读取到了A中未提交的数据。

脏读现象在生产环境中并不常发生,从上面的例子中就可以发现,脏读发生的条件是需要事务的隔离级别为READ UNCOMMITTED,而目前绝大部分的数据库都至少设置成READ COMMITTED。

不可重复读(non-repeatable read):指的是在一个事务执行过程中,读取到其他事务已提交的数据,导致两次读取的结果不一致的问题。

image-20210628232423971

A读取到了B中已经提交的事务,导致2次读取的结果不一致的问题。

幻读(phantom read):指一个事务执行过程中,读取到了其他事务新插入数据,导致两次读取 的结果不一致。

image-20210628231534024

A读取到了B事务中新插入的数据,这种现象称为幻读。

不可重复读和幻读的区别在于不可重复读是读到的是其他事务修改或者删除的数据,而幻读读到的是其它事务新插入的数据。

四种隔离级别

前面提到过为了解决以上问题,所以就有了“隔离级别”的概念,不同的隔离级别解决不同的读一致性问题,在了解隔离级别前,首先要清楚较低级别的隔离通常可以执行更高的并发,系统的开销也更低。所以很多时候我们需要在两者之间寻找一个平衡点。

SQL 标准的事务隔离级别包括:

读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。

读未提交(read uncommitted):在READ UNCOMMITTED级别下,事务的修改,即使没有提交,对其他事务也是可见的。

读提交(read committed):一个事务提交之后,它所做的修改才会被其他事务看到。这个级别有时候也叫做不可重复读(nonrepeatable read),因为两次执行同样的查询,可能会得到不一样的结果。

可重复读(repeatable read):一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的,该级别保证了在同一个事务中多次读取同样记录的结果是一致的。它解决了脏读的问题,在理论上并未解决幻读问题,不过InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)解决了幻读的问题。

串行化(serializable ):对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。它通过强制事务串行执行,避免了前面说的幻读的问题。会在读取的每一行数据上都加上锁,所以可能导致大量的超时和锁争用问题。

image-20210628230319534

Oracle数据库默认支持的隔离级别是READ COMMITTED,不支持READUNCOMMITTED和REPEATABLE READ的事务隔离级别。

InnoDB存储引擎默认支持的隔离级别是REPEATABLE READ。

总结

MySQL事务不管在面试中还是在开发过程中都尤为重要,这篇文章都是从理论角度介绍关于MySQL事务的基本知识,对于初学者来说,这些理论知识可以让你对MySQL的事务有一个整体的感知,MySQL博大精深很多知识需要我们主动深入理解,自主思考永远比被动接受更重要。

最后,如果你对文章有什么疑问,可以留言哈。

References

作者

Fahsa

发布于

2021-06-28

更新于

2021-06-28

许可协议

评论