MySQL事务原理&实战官方精译

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL事务原理&实战官方精译相关的知识,希望对你有一定的参考价值。

事务隔离级别

事务隔离是数据库处理的基础之一。隔离是I中的首字母 ​​ACID​​ ; 隔离级别是在多个事务同时进行更改和执行查询时,对结果的性能和可靠性,一致性和可重复性之间的平衡进行微调的设置。

​InnoDB​​​提供由SQL描述的所有四个事务隔离级别:1992标准: ​​READ UNCOMMITTED​​​, ​​READ COMMITTED​​​, ​​REPEATABLE READ​​​,和 ​​SERIALIZABLE​​​。​​InnoDB​​​is 的默认隔离级别​​REPEATABLE READ​​。

用户可以更改单个会话的隔离级别,也可以更改与该​​SET TRANSACTION​​​语句的所有后续连接。要为所有连接设置服务器的默认隔离级别,请使用 ​​--transaction-isolation​​​命令行或选项文件中的选项。有关隔离级别和级别设置语法的详细信息,请参见 ​​第13.3.6节“SET TRANSACTION语法”​​。

​InnoDB​​​支持使用不同的​​锁定​​​策略在此描述的每个事务隔离级别 。您可以强制执行与默认​​REPEATABLE READ​​​级别的高度一致性,以便在​​ACID​​​合规性很重要的关键数据上进行操作 。或者,您可以放宽一致性规则, ​​READ COMMITTED​​​甚至 ​​READ UNCOMMITTED​​​在批量报告等情况下,精确的一致性和可重复的结果不如最小化锁定开销的数量重要。 ​​SERIALIZABLE​​​执行更严格的规则​​REPEATABLE READ​​​,主要用于​​XA​​​等特殊情况事务和解决并发和​​死锁​​问题 。

以下列表描述了mysql如何支持不同的事务级别。该列表从最常用的级别到最少使用的级别。

  • ​READ COMMITTED​​即使在同一事务中,每次一致的读取都会设置并读取自己的新快照。有关一致读取的信息,请参见 ​​第14.5.2.3节“一致性非锁定读取”​​。
    对于锁定读取(​​SELECT​​ 使用​​FOR UPDATE​​或​​LOCK IN SHARE MODE​​),​​UPDATE​​ 语句和​​DELETE​​ 语句,​​InnoDB​​只锁定索引记录,而不锁定它们之前的间隔,从而允许在锁定记录旁边自由插入新记录。间隙锁定仅用于外键约束检查和重复键检查。
    因为禁用了间隙锁定,所以可能会出现幻影问题,因为其他会话可以将新行插入到间隙中。有关幻影的信息,请参见 ​​第14.5.4节“幻影行”​​。
    如果使用​​READ COMMITTED​​,则 必须使用基于行的二进制日志记录。
    使用​​READ COMMITTED​​有其他影响:
  • 对于​​UPDATE​​或 ​​DELETE​​语句, ​​InnoDB​​仅锁定更新或删除的行。在MySQL已经评估​​WHERE​​条件之后,释放不匹配行的记录锁 。这大大降低了死锁的可能性,但它们仍然可以发生。
  • 对于​​UPDATE​​语句,如果一行已经被锁定,​​InnoDB​​ 执行“ 半连续 ”读取,将最新的提交版本返回给MySQL,以便MySQL可以确定该行是否与​​WHERE​​条件 匹配 ​​UPDATE​​。如果行匹配(必须更新),则MySQL再次读取该行,这次​​InnoDB​​要么锁定它,要么等待锁定。

考虑下面的例子,从这个表开始:

CREATE TABLE t (a INT NOT NULL, b INT) ENGINE = InnoDB;
INSERT INTO t VALUES (1,2),(2,3),(3,2),(4,3),(5,2);
COMMIT;

在这种情况下,表没有索引,因此搜索和索引扫描使用隐藏的聚簇索引进行记录锁定(请参见 ​​第14.8.2.1节“聚簇索引”和“二级索引”​​)。

假设一个客户​​UPDATE​​使用这些语句执行 :

SET autocommit = 0;
UPDATE t SET b = 5 WHERE b = 3;

假设第二个客户端 ​​UPDATE​​通过在第一个客户端之后执行这些语句来执行:

SET autocommit = 0;
UPDATE t SET b = 4 WHERE b = 2;

由于​​InnoDB​​每个执行 ​​UPDATE​​时,它首先获取用于各行的排他锁,然后确定是否对其进行修改。如果​​InnoDB​​不修改该行,则释放该锁。否则, ​​InnoDB​​保持锁直到交易结束。这会影响事务处理,如下所示。

当使用默认的​​REPEATABLE READ​​ 隔离级别时,第一个 ​​UPDATE​​获取x锁并且不释放它们中的任何一个:

x-lock(1,2); retain x-lock
x-lock(2,3); update(2,3) to (2,5); retain x-lock
x-lock(3,2); retain x-lock
x-lock(4,3); update(4,3) to (4,5); retain x-lock
x-lock(5,2); retain x-lock

第二个​​UPDATE​​块试图获取任何锁(因为第一个更新保留了所有行上的锁),并且在第一个​​UPDATE​​提交或回滚之前不会继续:

x-lock(1,2); block and wait for first UPDATE to commit or roll back

如果​​READ COMMITTED​​使用它,则第一个​​UPDATE​​获取x锁并释放那些不修改的行:

x-lock(1,2); unlock(1,2)
x-lock(2,3); update(2,3) to (2,5); retain x-lock
x-lock(3,2); unlock(3,2)
x-lock(4,3); update(4,3) to (4,5); retain x-lock
x-lock(5,2); unlock(5,2)

对于第二个​​UPDATE​​, ​​InnoDB​​执行 “ 半连续 ”读取,将每行的最新提交版本返回给MySQL,以便MySQL可以确定该行是否匹配以下 ​​WHERE​​条件 ​​UPDATE​​:

x-lock(1,2); update(1,2) to (1,4); retain x-lock
x-lock(2,3); unlock(2,3)
x-lock(3,2); update(3,2) to (3,4); retain x-lock
x-lock(4,3); unlock(4,3)
x-lock(5,2); update(5,2) to (5,4); retain x-lock

使用​​READ COMMITTED​​ 隔离级别的效果与启用不建议的 ​​innodb_locks_unsafe_for_binlog​​ 配置选项相同,但有以下例外:

​READ COMMITTED​​因此提供更好,更灵活的控制 ​​innodb_locks_unsafe_for_binlog​​。

  • ​READ UNCOMMITTED​​​​SELECT​​​语句以非锁定的方式执行,但是可能会使用一个可能的早期版本的行。因此,使用这个隔离级别,这样的读取是不一致的。这也被称为 ​​脏读​​。否则,这个隔离级别就像 ​​READ COMMITTED​​。
  • ​SERIALIZABLE​​​这个级别是​​REPEATABLE READ​​,但​​InnoDB​​隐式转换所有简单的​​SELECT​​ 语句,​​SELECT ... LOCK IN SHARE MODE​​如果​​autocommit​​被禁用。如果 ​​autocommit​​启用,则 ​​SELECT​​是它自己的事务。因此,它被认为是只读的,并且如果作为一致的(非锁定的)读取来执行,则可以被序列化,并且不需要阻塞其他事务。(​​SELECT​​如果其他事务已经修改了所选的行,强制一个普通 的阻止,禁用 ​​autocommit​​。)

 

实战:

事务的ACID特性

事务必须同时满足ACID的特性:

  • 原子性(Atomicity)。事务中的所有操作要么全部执行成功,要么全部取消。
  • 一致性(Consistency)。事务开始之前和结束之后,数据库完整性约束没有破坏。
  • 隔离性(Isolation)。事务提交之前对其它事务不可见。
  • 持久性(Durability)。事务一旦提交,其结果是永久的。

事务的分类

从事务理论的角度可以把事务分为以下几种类型:

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

扁平事务

扁平事务(Flat Transactions)是事务类型中最简单但使用最频繁的事务。在扁平事务中,所有的操作都处于同一层次,由BEGIN/START TRANSACTION开始事务,由COMMIT/ROLLBACK结束,且都是原子的,要么都执行,要么都回滚。因此扁平事务是应用程序成为原子操作的基本组成模块。扁平事务一般有三种不同的结果: 

1.事务成功完成。在平常应用中约占所有事务的96%。 

MySQL事务原理&实战【官方精译】_数据

2.应用程序要求停止事务。比如应用程序在捕获到异常时会回滚事务,约占事务的3%。 

MySQL事务原理&实战【官方精译】_数据_02

3.外界因素强制终止事务。如连接超时或连接断开,约占所有事务的1%。 

MySQL事务原理&实战【官方精译】_数据_03

扁平事务的主要限制是不能提交或者回滚事务的某一部分。如果某一事务中有多个操作,在一个操作有异常时并不希望之的操作全部回滚,而是保存前面操作的更改。扁平事务并不能支持这样的事例,因此就出现了带有保存节点的扁平事务。

带有保存节点的扁平事务

带有保存节点的扁平事务(Flat Transactions with Savepoints)允许事务在执行过程中回滚到较早的一个状态,而不是回滚所有的操作。保存点(Savepoint)用来通知系统应该记住事务当前的状态,以便当之后发生错误时,事务能回到保存点当时的状态。

对于扁平事务来说,在事务开始时隐式地设置了一个保存点,回滚时只能回滚到事务开始时的状态。下图是回滚到某个保存节点的实例: 

MySQL事务原理&实战【官方精译】_隔离级别_04

链事务

链事务(Chained Transaction)是指一个事务由多个子事务链式组成。前一个子事务的提交操作和下一个子事务的开始操作合并成一个原子操作,这意味着下一个事务将看到上一个事务的结果,就好像在一个事务中进行的一样。这样,在提交子事务时就可以释放不需要的数据对象,而不必等到整个事务完成后才释放。其工作方式如下: 

MySQL事务原理&实战【官方精译】_回滚_05

 

链事务与带保存节点的扁平事务不同的是,链事务中的回滚仅限于当前事务,相当于只能恢复到最近的一个保存节点,而带保存节点的扁平事务能回滚到任意正确的保存点。但是,带有保存节点的扁平事务中的保存点是易失的,当发生系统崩溃是,所有的保存点都将消失,这意味着当进行恢复时,事务需要从开始处重新执行。

嵌套事务

嵌套事务(Nested Transaction)是一个层次结构框架。由一个顶层事务(top-level transaction)控制着各个层次的事务。顶层事务之下嵌套的事务成为子事务(subtransaction),其控制着每一个局部的操作,子事务本身也可以是嵌套事务。因此,嵌套事务的层次结构可以看成是一颗树,其结构如下图所示。 

MySQL事务原理&实战【官方精译】_隔离级别_06

分布式事务

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

例如一个银行用户从招商银行的账户向工商银行的账户转账1000元,这里需要用到分布式事务,因为不能仅调用某一家银行的数据库就完成任务。 

MySQL事务原理&实战【官方精译】_隔离级别_07

事务的隔离级别

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

  • READ UNCOMMITTED
  • READ COMMITTED
  • REPEATABLE READ
  • SERIALIZABLE

这些隔离级别定义了事务中哪些数据的改变对其它事务可见,哪些数据的改变对其它事务不可见,事务的隔离级别可以使用以下语句进行设置。 

MySQL事务原理&实战【官方精译】_隔离级别_08

READ UNCOMMITTED

读取未提交内容。在该隔离级别下,所有事务都可以看到其它未提交事务的执行结果。如下图所示: 

MySQL事务原理&实战【官方精译】_数据_09

 

事务2查询到的数据是事务1中修改但未提交的数据,但因为事务1回滚了数据,所以事务2查询的数据是不正确的,因此出现了脏读的问题。

READ COMMITTED

读取提交内容。在该隔离级别下,一个事务从开始到提交之前对数据所做的改变对其它事务是不可见的,这样就解决在READ-UNCOMMITTED级别下的脏读问题。但如果一个事务在执行过程中,其它事务的提交对该事物中的数据发生改变,那么该事务中的一个查询语句在两次执行过程中会返回不一样的结果。如下图所示: 

MySQL事务原理&实战【官方精译】_回滚_10


事务2执行update语句但未提交前,事务1的前两个select操作返回结果是相同的。但事务2执行commit操作后,事务1的第三个select操作就读取到事务2对数据的改变,导致与前两次select操作返回不同的数据,因此出现了不可重复读的问题。

REPEATABLE READ

可重复读。这是MySQL的默认事务隔离级别,能确保事务在并发读取数据时会看到同样的数据行,解决了READ-COMMITTED隔离级别下的不可重复读问题。MySQL的InnoDB存储引擎通过多版本并发控制(Multi_Version Concurrency Control, MVCC)机制来解决该问题。在该机制下,事务每开启一个实例,都会分配一个版本号给它,如果读取的数据行正在被其它事务执行DELETE或UPDATE操作(即该行上有排他锁),这时该事物的读取操作不会等待行上的锁释放,而是根据版本号去读取行的快照数据(记录在undo log中),这样,事务中的查询操作返回的都是同一版本下的数据,解决了不可重复读问题。其原理如下图所示: 

MySQL事务原理&实战【官方精译】_隔离级别_11

虽然该隔离级别下解决了不可重复读问题,但理论上会导致另一个问题:幻读(Phantom Read)。正如上面所讲,一个事务在执行过程中,另一个事物对已有数据行的更改,MVCC机制可保障该事物读取到的原有数据行的内容相同,但并不能阻止另一个事务插入新的数据行,这就会导致该事物中凭空多出数据行,像出现了幻读一样,这便是幻读问题。如下图所示: 

MySQL事务原理&实战【官方精译】_隔离级别_12

事务2对id=1的行内容进行了修改并且执行了commit操作,事务1中的第二个select操作在MVCC机制的作用下返回的仍是v=1的数据。但事务3执行了insert操作,事务1第三次执行select操作时便返回了id=2的数据行,与前两次的select操作返回的值不一样。

需要说明的是,REPEATABLE-READ隔离级别下的幻读问题是SQL标准定义下理论上会导致的问题,MySQL的InnoDB存储引擎在该隔离级别下,采用了Next-Key Locking锁机制避免了幻读问题。Next-Key Locking锁机制将在后面的锁章节中讲到。

SERIALIZABLE

可串行化。这是事务的最高隔离级别,通过强制事务排序,使之不可能相互冲突,就是在每个读的数据行加上共享锁来实现。在该隔离级别下,可以解决前面出现的脏读、不可重复读和幻读问题,但也会导致大量的超时和锁竞争现象,一般不推荐使用。

 

 

总结:

四个级别逐渐增强,每个级别解决一个问题。事务级别越高,性能越差,大多数环境read committed 可以用.

4个隔离级别的特点;

===========================================================================================
       隔离级别               脏读(Dirty Read)          不可重复读(NonRepeatable Read)     幻读(Phantom Read) 
===========================================================================================

未提交读(Read uncommitted)        可能                            可能                       可能

已提交读(Read committed)          不可能                          可能                        可能

可重复读(Repeatable read)          不可能                          不可能                     可能

可串行化(Serializable )                不可能                          不可能                     不可能

===========================================================================================

作者:​​sunsky303​​



以上是关于MySQL事务原理&实战官方精译的主要内容,如果未能解决你的问题,请参考以下文章

MySQL锁和事务:InnoDB锁(MySQL 官方文档粗翻)

RocketMQ事务消息实战

MySQL的间隙锁

mysql repeatable-read 一次利用间隙锁解决幻读案例

mysql多个事务同时执行死锁(间隙锁)

Mysql加锁过程详解-innodb下的记录锁,间隙锁,next-key锁