数据库事务(简称:事务)是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。数据库事务通常包含了一个序列的对数据库的读/写操作。包含有以下两个目的:
当事务被提交给了数据库管理系统(DBMS),则DBMS需要确保该事务中的所有操作都成功完成且其结果被永久保存在数据库中,如果事务中有的操作没有成功完成,则事务中的所有操作都需要回滚,回到事务执行前的状态;同时,该事务对数据库或者其他事务的执行无影响,所有的事务都好像在独立的运行。
并非任意的对数据库的操作序列都是数据库事务。数据库事务拥有以下四个特性,习惯上被称之为ACID特性。
在InnoDB中,所有的用户活动都发生在事务中。如果启用了自动提交模式,则每个SQL语句单独形成一个事务。默认情况下,MySQL为每个新连接启动会话时都启用了自动提交模式,因此MySQL在每个SQL语句没有返回错误时都会在该语句之后进行提交。如果语句返回错误,则提交或回滚行为取决于错误。
启用了自动提交功能的会话可以执行多语句事务,方法是使用显式的START TRANSACTION
语句开始,并以COMMIT
或ROLLBACK
语句结束。
如果使用SET autocommit = 0
在会话中禁用自动提交模式,则该会话始终有一个打开的事务。COMMIT
或ROLLBACK
语句结束当前事务,并开始一个新事务。
如果一个会话在没有显式提交最后一个事务的情况下结束,MySQL回滚该事务。
有些语句隐式地结束事务,就好像在执行语句之前执行了COMMIT
。
COMMIT
意味着在当前事务中所做的更改是永久的,并且对其他会话可见。另一方面,ROLLBACK
语句取消当前事务所做的所有修改。COMMIT
和ROLLBACK
都会释放当前事务中设置的所有InnoDB锁。
ACID模型的一致性方面主要涉及InnoDB内部处理,以防止数据崩溃。相关的MySQL特性包括:
false
,此时切换到事务B,事务B开启事务,插入了一条学生数据,此时切换回事务A,事务A提交的时候发现了一条自己没有修改过的数据,这就是幻读,就好像发生了幻觉一样。幻读出现的前提是并发的事务中有事务发生了插入、删除操作。在InnoDB中,通过锁和MVCC来实现事务的隔离性,并且InnoDB致力于将MVCC与传统的锁相结合。一般情况下,我们不需要以手动的方式保证事务的隔离性,而是通过设置事务的隔离级别来保证。InnoDB提供了SQL:1992标准中描述的所有四种事务隔离级别:
READ UNCOMMITTED
:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。READ COMMITTED
:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。REPEATABLE READ
:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。SERIALIZABLE
:最高的隔离级别,所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。InnoDB默认的隔离级别是REPEATABLE READ
。用户可以使用SET TRANSACTION
语句更改单个会话或所有后续连接的隔离级别。要为所有连接设置服务器的默认隔离级别,请在命令行或选项文件中使用--transaction-isolation
选项。
InnoDB是一个多版本的存储引擎。它保留有关已更改行的旧版本的信息,以支持并发和回滚等事务性特性。这些信息存储在undo表空间中称为回滚段的数据结构中。在内部,InnoDB在数据库中存储的每一行添加三个字段:
DB_TRX_ID
字段:表示插入或更新该行的最后一个事务的事务标识符。DB_ROLL_PTR
字段,称为回滚指针:回滚针指向该行写入回滚段的undo日志记录。DB_ROW_ID
字段包含一个行ID:随着新行插入而单调增加。在一个事务中:
UPDATE
、DELETE
和INSERT
操作时:
DB_TRX_ID
字段存储当前事务的标识符,以便其他事务能够正确读取相应版本的数据。SELECT
操作时:根据某种原则选择相应的版本进行读取 (见下文一致性非锁定读)。上述这一过程就称为MVCC。
一致性读(也就是一个普通的SELECT
语句)是InnoDB在READ COMMITTED
和REPEATABLE READ
隔离级别下处理SELECT
语句的默认模式。一致性读不会在它访问的表上设置任何锁,因此其他会话可以在对表执行一致性读的同时自由地修改这些表。一致性读意味着InnoDB使用多版本来为查询提供数据库在某个时间点的快照。查询会看到在该时间点之前提交的事务所做的更改,而不会看到后面或未提交的事务所做的更改。该规则的例外情况是,查询在同一事务中看到前面的语句所做的更改。此异常会导致以下异常:
SELECT
可以看到更新行的最新版本。假设您以默认的REPEATABLE READ
隔离级别运行。则同一事务中的所有一致性读都读取由该事务中的第一个一致性读建立的快照。当你发出一个一致性读时,InnoDB会给你的事务一个查询看到数据库的时间点。如果另一个事务在您指定的时间点之后删除了一行并提交,则不会看到该行已被删除。插入和更新的处理方式类似。您可以通过提交事务,然后执行另一个SELECT
或START TRANSACTION WITH CONSISTENT SNAPSHOT
来提前您的时间点。
在下面的示例中,会话A只有在B提交了插入并且会话A也提交了插入时才会看到B插入的行,因此时间点提前到B提交之后。
Session A Session B
SET autocommit=0; SET autocommit=0;
time
| SELECT * FROM t;
| empty set
| INSERT INTO t VALUES (1, 2);
|
v SELECT * FROM t;
empty set
COMMIT;
SELECT * FROM t;
empty set
COMMIT;
SELECT * FROM t;
---------------------
| 1 | 2 |
---------------------
如果在同一个事务中查询数据,然后插入或更新相关数据,则常规SELECT
语句无法提供足够的保护。其他事务可以更新或删除您刚刚查询的相同行。InnoDB支持两种类型的锁读,提供额外的安全性:
SELECT ... FOR SHARE
:在读取的任何行上设置共享锁。其他会话可以读取这些行,但在事务提交之前不能修改它们。如果这些行中的任何一行被另一个尚未提交的事务更改,则查询将等待,直到该事务结束,然后使用最新的值。SELECT ... FOR UPDATE
:
UPDATE
语句一样。SELECT…FOR SHARE
,或者从某些事务隔离级别读取数据。所有由FOR SHARE
和FOR UPDATE
查询设置的锁在事务提交或回滚时被释放。
外部语句中的锁定读子句不会锁定嵌套子查询中的表行,除非在子查询中也指定了锁定读子句。例如,下面的语句不会锁定表t2
中的行:
SELECT * FROM t1 WHERE c1 = (SELECT c1 FROM t2) FOR UPDATE;
要锁定表t2
中的行,在子查询中添加一个锁读子句:
SELECT * FROM t1 WHERE c1 = (SELECT c1 FROM t2 FOR UPDATE) FOR UPDATE;
如果一行被事务锁住,SELECT…FOR UPDATE
或SELECT…FOR SHARE
事务请求同一行锁时,必须等待阻塞事务释放行锁。此行为可防止事务更新或删除由其他事务查询更新的行。但是,如果您希望查询在所请求的行被锁定时立即返回,或者从结果集中排除锁定的行是可以接受的,则没有必要等待行锁被释放。
为了避免等待其他事务释放行锁,可以使用NOWAIT
和SKIP LOCKED
选项“
NOWAIT
:使用NOWAIT
的锁读永远不会等待获取行锁。查询立即执行,如果所请求的行被锁定,则会失败并出现错误。SKIP LOCKED
:使用SKIP LOCKED
的锁读从不等待获取行锁。查询立即执行,从结果集中删除锁定的行。NOWAIT
和SKIP LOCKED
仅适用于行级锁。
下面的示例演示了NOWAIT
和SKIP LOCKED
。会话1启动一个事务,该事务对单个记录使用行锁。会话2尝试使用NOWAIT
选项对同一条记录进行锁定读取。由于所请求的行被会话1锁定,锁定读操作立即返回一个错误。在会话3中,使用SKIP LOCKED
的锁定读取返回请求的行,除了被会话1锁定的行。
# Session 1:
mysql> CREATE TABLE t (i INT, PRIMARY KEY (i)) ENGINE = InnoDB;
mysql> INSERT INTO t (i) VALUES(1),(2),(3);
mysql> START TRANSACTION;
mysql> SELECT * FROM t WHERE i = 2 FOR UPDATE;
+---+
| i |
+---+
| 2 |
+---+
# Session 2:
mysql> START TRANSACTION;
mysql> SELECT * FROM t WHERE i = 2 FOR UPDATE NOWAIT;
ERROR 3572 (HY000): Do not wait for lock.
# Session 3:
mysql> START TRANSACTION;
mysql> SELECT * FROM t FOR UPDATE SKIP LOCKED;
+---+
| i |
+---+
| 1 |
| 3 |
+---+
InnoDB支持多粒度锁,允许行锁和表锁共存。
InnoDB实现了标准的行级锁,其中有两种类型的锁,共享锁(S)和独占锁(X)。
如果事务T1
持有行r
上的共享锁,那么来自不同事务T2
的请求对行r
上的锁的处理如下:
T2
对S
锁的请求可以立即被授予。因此,T1
和T2
都对r
持有S
锁。T2
对X
锁的请求不能立即被授予。如果事务T1
持有行r
上的独占锁,则来自某个不同事务T2
的对行r
上任意类型锁的请求不能立即被授予。相反,事务T2
必须等待事务T1
释放对行r
的锁。
根据锁的粒度可以将行锁划分为以下几种:
SELECT c1 FROM t WHERE c1 = 10 For UPDATE;
将防止任何其他事务插入、更新或删除t.c1
值为10
的行。SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 For UPDATE;
将防止其他事务将值15
插入到列t.c1
中,无论列中是否已经存在这样的值,因为范围中所有现有值之间的间隙被锁定。意向锁是表级别的锁,它指示事务将对表中的某一行需要哪种类型的锁。意向锁不阻塞任何东西,意向锁的主要目的是显示某人正在锁定表中的一行,或者将要锁定表中的一行。有两种类型的意向锁:
例如,SELECT…FOR SHARE
设置IS锁,SELECT…FOR UPDATE
设置一个IX锁。意向锁的协议如下:
表级锁类型兼容性如下表所示:
X | IX | S | IS | |
---|---|---|---|---|
X | 冲突 | 冲突 | 冲突 | 冲突 |
IX | 冲突 | 兼容 | 冲突 | 兼容 |
S | 冲突 | 冲突 | 兼容 | 兼容 |
IS | 冲突 | 兼容 | 兼容 | 兼容 |
如果锁与现有锁兼容,则将锁授予请求事务,但如果它与现有锁冲突,则不会授予。事务等待,直到冲突的现有锁被释放。如果锁请求与现有锁冲突,并且由于会导致死锁而无法授予,则会发生错误。
AUTO-INC
锁是一种特殊的表级锁,用于在具有AUTO_INCREMENT
列的表中插入事务。在最简单的情况下,如果一个事务正在向表中插入值,那么任何其他事务都必须等待对该表进行自己的插入,以便第一个事务插入的行接收连续的主键值。
InnoDB使用不同的锁策略支持这里描述的每一种事务隔离级别。下文详细描述了MySQL如何支持不同的事务级别:
REPEATABLE READ
:
SELECT
语句,这些SELECT
语句彼此之间也是一致的。SELECT
、UPDATE
和DELETE
语句:锁的使用取决于语句使用的查询索引和查询条件:
READ COMMITTED
:
SELECT
、UPDATE
语句和DELETE
语句:InnoDB只使用记录锁锁定找到的索引记录,间隙锁仅用于外键约束检查和重复键检查。READ UNCOMMITTED
:
SELECT
语句SELECT
语句:以非锁定方式执行,因此可能使用行的早期版本,所以使用这个隔离级别,这样的读取是不一致的,这也被称为脏读。SELECT
、UPDATE
和DELETE
语句:与READ COMMITTED
类似。SERIALIZABLE
:
SELECT
语句SELECT
语句:
SELECT
语句转换为SELECT…FOR SHARE
语句。SELECT
拥有自己的事务。SELECT
、UPDATE
和DELETE
语句:与REPEATABLE READ
类似。ACID模型的持久性方面涉及MySQL软件特性与特定硬件配置的交互。由于有许多可能性取决于您的CPU、网络和存储设备的能力,因此这方面是最复杂的,无法提供具体的指导方针。
InnoDB使用争用感知事务调度(CATS)算法对等待锁的事务进行优先级排序。当多个事务等待同一对象上的锁时,CATS算法确定哪个事务首先收到锁。
CATS算法通过分配调度权重来确定等待事务的优先级,调度权重是根据事务阻塞的事务数量计算的。例如,如果两个事务正在等待同一对象上的锁,那么阻塞事务最多的事务将被分配更大的调度权重。如果权重相等,则优先考虑等待时间最长的事务。
START TRANSACTION
[transaction_characteristic [, transaction_characteristic] ...]
transaction_characteristic: {
WITH CONSISTENT SNAPSHOT
| READ WRITE
| READ ONLY
}
COMMIT
ROLLBACK
SET autocommit = {0 | 1}
这些语句提供对事务使用的控制:
START TRANSACTION
开始一个新的事务。COMMIT
提交当前事务,使其更改永久保存。ROLLBACK
回滚当前事务,取消其更改。SET autocommit
禁用或启用当前会话的默认自动提交模式。默认情况下,MySQL运行时启用自动提交模式。这意味着,当不在事务内时,每个语句都是原子的,就像被START transaction
和COMMIT
包围一样。您不能使用ROLLBACK
来撤消该效果;但是,如果在语句执行期间发生错误,则回滚语句。
要隐式地禁用自动提交模式,使用START TRANSACTION
语句。使用START TRANSACTION
,自动提交将保持禁用状态,直到使用COMMIT
或ROLLBACK
结束事务。然后自动提交模式恢复到以前的状态。
START TRANSACTION
允许几个控制事务特征的修饰符。要指定多个修饰符,用逗号分隔它们。
WITH CONSISTENT
快照修饰符对能够创建快照的存储引擎启动一致性读操作。这只适用于InnoDB。其效果与发出START
事务,然后从任何InnoDB表中进行SELECT
是一样的。WITH CONSISTENT
快照修饰符不更改当前事务隔离级别,因此仅当当前隔离级别允许一致性读取时,它才提供一致性快照。READ WRITE
和READ ONLY
修饰符设置事务访问模式。它们允许或禁止对事务中使用的表进行更改。READ ONLY
限制防止事务修改或锁定对其他事务可见的事务表和非事务表。
READ ONLY
可确保在无法自动确定只读状态的情况下应用这些优化。READ WRITE
。某些语句无法回滚。通常,这些语句包括数据定义语言(DDL)语句,例如那些创建或删除数据库的语句,那些创建、删除或修改表或存储例程的语句。
您应该设计您的事务不包含这样的语句。如果您在事务的早期发出了一条不能回滚的语句,然后另一条语句失败,那么在这种情况下,不能通过发出ROLLBACK
语句回滚事务的全部效果。
本节中列出的语句(以及它们的同义词)隐式地结束当前会话中活动的任何事务,就像在执行语句之前执行了COMMIT
一样。
这些语句中的大多数在执行后还会导致隐式提交。其目的是在其自己的特殊事务中处理每个这样的语句。事务控制和锁定语句是例外:如果隐式提交发生在执行之前,则不会在执行之后发生另一次提交。
SAVEPOINT identifier
ROLLBACK TO [SAVEPOINT] identifier
RELEASE SAVEPOINT identifier
SAVEPOINT
语句设置一个名称为identifier
的命名事务保存点。如果当前事务具有同名的保存点,则删除旧的保存点并设置新保存点。
ROLLBACK TO SAVEPOINT
语句将事务回滚到指定的保存点,而不终止事务。当前事务在设置保存点之后对行所做的修改在回滚中被撤销,但是InnoDB不会释放保存点之后存储在内存中的行锁。(对于新插入的行,锁信息由该行存储的事务ID携带;锁不会单独存储在内存中。在这种情况下,行锁在撤消操作中被释放。)晚于指定保存点设置的保存点将被删除。
RELEASE SAVEPOINT
语句从当前事务的保存点集中删除指定的保存点。不发生提交或回滚。如果保存点不存在,则会产生错误。
如果执行COMMIT
或ROLLBACK
操作而不指定保存点,则会删除当前事务的所有保存点。
当调用存储函数或激活触发器时,将创建新的保存点级别。先前关卡的保存点将不可用,因此不会与新关卡的保存点发生冲突。当函数或触发器终止时,它创建的所有保存点都会被释放,并且恢复到以前的保存点级别。