概念
事务可以理解为这样的一组 SQL 语句:
由同一个用户输入
以所有的修改(
INSERT
UPDATE
DELETE
) 被提交(持久化)或回滚(撤销)为终结
事务的开始与结束
开始事务
自动提交(autocommit)是 MySQL 的默认工作模式,在自动提交的工作模式下,每条 SQL 语句是一个完整的事务,自动开始
可以通过修改 MySQL 配置文件,添加
autocommit = 0
,关闭所有连接的自动提交。数据库连接的第一条 SQL 或者前一个事务结束后的第一条 SQL 都将开始一个新的事务通过
set @@global.autocommit = 0
或set global autocommit = 0
也可以永久关闭自动提交,且不用重启服务可以通过
SET autocommit = 0
只在当前连接关闭自动提交。关闭自动提交后的第一条 SQL 或者前一个事务结束后的第一条 SQL 都将开始一个新的事务可以通过
START TRANSACTION
在当前连接临时关闭自动提交。关闭自动提交后的第一条 SQL 将开始一个新的事务。事务结束后,回到原提交模式。往往在自动提交模式下使用,事务结束后仍回到自动提交模式,之后的 SQL 不会再开始新的事务(当然,在非自动提交模式下也可以使用,事务结束后仍回到非自动提交模式,不过这样做有什么意义呢)
结束事务
在自动提交模式下,每一条 SQL 是一个完整的事务,自动结束
通过
COMMIT
提交或ROLLBACK
回滚(未指定保存点时才会回滚并结束整个事务),显式地结束事务通过
START TRANSACTION
开启新的事务,自动结束前一个未结束的事务如果还没有结束事务,应用程序就结束了,MySQL 将自动执行一条
ROLLBACK
,隐式地结束事务在数据定义语言(如
CREATE
ALTER
DROP
GRANT
等)之前和之后,MySQL 会自动执行一条COMMIT
,隐式地提交并结束事务。因此这些语句不能被撤销,且在这些语句之前的未提交的语句也会被一起提交。LOCK TABLES
在锁定表之前会隐式地提交事务UNLOCK TABLES
在释放锁的同时会隐式地提交事务(前提是已通过LOCK TABLES
获取了表锁)
保存点
设置保存点,在回滚时可以指定回滚到的保存点,保存点之后的修改被撤销,之前的修改暂未撤销,也没有持久化
SAVEPOINT <保存点名称>
ROLLBACK TO SAVEPOINT <保存点名称>
事务的 ACID 属性
原子性 Atomic
一个事务要么全部执行,要么全部不执行,不会结束在中间某个环节。事务执行过程中发生错误会被回滚到事务开始前的状态,就像从来没执行过一样。
一致性 Consistency
在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
隔离性 Isolation
事务的执行不能互相干扰,即一个事务内的操作和使用的数据对其他并发事务是隔离的,不可见的。事务将数据库由一个一致性状态转换为另一个一致性状态时,不能出现非本事务内的操作或数据,即不能被其他事务干扰。
持久性 Durability
一个事务一旦提交,它对数据库的修改应该是永久性的,不会因接下来的操作或故障影响已提交的事务
事务的隔离级别
事务的并发问题
脏读(未提交的读)
事务 A 读取到事务 B 未提交的数据,事务 B 可能会回滚,则事务 A 读取到的就是脏数据,这种现象叫做脏读。
防范脏读,需要只读已提交的数据,未提交的数据不读取。
不可重复读(重复读的结果不一致)
事务 A 读取数据,事务 B 对数据作了更新并提交,事务 A 再次读取数据时内容发生变化(特指行内数据的变化,注意与幻读区分),这种现象叫做不可重复读。
防范不可重复读,需要锁定第一次查询结果集中的所有行。
幻读(幻象读取)
事务 A 读取数据,事务 B 插入数据或更新数据(更新后的数据恰好与事务 A 中 WHERE 子句匹配)并提交,事务 A 再次读取数据时,发现结果集的行数变多了,多出来的行就是幻影行,这就是幻读现象(之一)。
幻读比不可重复读更难防范,锁定第一次查询结果集中的所有行,不能阻止导致幻影行的插入或更新操作。
参考 MySQL官方文档
A row that appears in the result set of a query, but not in the result set of an earlier query. For example, if a query is run twice within a transaction, and in the meantime, another transaction commits after inserting a new row or updating a row so that it matches the WHERE clause of the query.
This occurrence is known as a phantom read. It is harder to guard against than a non-repeatable read, because locking all the rows from the first query result set does not prevent the changes that cause the phantom to appear.
Among different isolation levels, phantom reads are prevented by the serializable read level, and allowed by the repeatable read, consistent read, and read uncommitted levels.
隔离级别
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
读未提交(read-uncommitted) | 是 | 是 | 是 |
读已提交(read-committed) | 否 | 是 | 是 |
可重复读(repeatable-read)【默认】 | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
隔离级别越高,事务间影响越少,并发性能越差。
read-uncommitted
隔离级别以上避免了脏读。
read-committed
和 repeatable-read
通过 MVCC
(多版本并发控制)在 SELECT
时使用快照读,避免了不可重复读。
虽然 MVCC
使用快照读可以避免上文中介绍幻读现象时描述的特定场景(幻读还有其他产生方式),并不能完全杜绝幻读现象,比如以下两个场景:
#事务A
SELECT id,name,amount FROM demo WHERE id > 5; #比如结果集有 id 为 6 和 7 的两条数据
#事务B
INSERT INTO demo VALUES(8,'a',100);
COMMIT;
#事务A
UPDATE demo SET amount = amount + 100 WHERE id > 5; #更新了三条记录
SELECT id,name,amount FROM demo WHERE id > 5; #再次查询时结果集中是 id 为 6、7和8的三条数据。幻影行还是出现了
#事务A
SELECT id,name,amount FROM demo WHERE id > 5; #比如结果集有 id 为 6 和 7 的两条数据
#事务B
INSERT INTO demo VALUES(8,'a',100);
COMMIT;
#事务A
INSERT INTO demo VALUES(8,'a',100); #插入失败,提示主键重复???幻觉了
MySQL 中防范幻读可以使用 SELECT ... FOR UPDATE
,MySQL 使用 行锁和间隙锁 协作,不仅锁定结果集中的行,还锁定结果集的间隙,从而完全避免幻读。
争议
网上有观点认为,隔壁事务的新增或删除导致的两次读取的结果不一致,这种现象不应该叫幻读,也应该是不可重复读的一种。
确实,单从现象上看,这种现象确实是导致了前后读取的结果不一致,似乎符合不可重复读的概念。个人认为,幻读和不可重复读的区别主要在防范的手段上:不可重复读的重点是行内数据的变化,通过行锁即可防范;而幻读的重点是产生了幻影行,通过行锁无法防范。
网上有观点认为,MySQL 的隔离级别
repeatable-read
解决了幻读问题产生这种说法的原因有两种:
一、MySQL 的 MVCC 解决了部分幻读现象,有人就误认为(或者在表述上不严谨,让读者误以为)解决了所有幻读
二、MySQL 的隔离级别
repeatable-read
有机制(前文所述的SELECT ... FOR UPDATE
加锁)可以解决所有幻读现象,但并不是说,你可以无所顾忌的像单用户一样,无论怎么操作都不会产生幻读,而是要去使用或者叫“激活”这种机制。只说 MySQL 的隔离级别repeatable-read
解决了幻读是不严谨的,是容易引起误导的。