事务

事务就是要保证一组数据库操作,要么全部成功,要么全部失败。MySQL 中的事务是在存储引擎层实现的。

事务的启动方式

1、显式启动事务语句, beginstart transaction。配套的提交语句是 commit,回滚语 句是 rollback
2、set autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个select语句,这个事务就启动了, 而且并不会自动提交。这个事务持续存在直到你主动执行commitrollback语句,或者断开连接。

ACID

Atomicity(原子性)

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

Consistency(一致性)

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

Isolation(隔离性)

事务的隔离性要求每个读写事务的对象与其他事务的操作对象能相互隔离,即该事务提交前对其他事务都不可见。

Durability(持久性)

事务一旦提交,其结果就是永久性的,即使发生宕机等故障,数据库也能将数据恢复。

事务的分类

扁平事务(flat transactions)

事务类型中最简单的一种,也可能是使用最频繁的事务。在扁平事务中,所有操作都处于同一层次,其由 begin work开始, 由 commit 或者 rollback work结束。处于之间的操作是原子的,要么都执行,要么都回滚。

带有保存点的扁平事务(flat transactions with savepoints)

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

链事务(chained transactions)

在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式的传给下一个要开始的事务。注意,提交事务操作和开始下一个事务操作将 合并为一个原子操作。也就是下一个事务将看到上一个事务的结果,就像在一个事务中进行的。

嵌套事务(nested transactions)

嵌套事务是一个层次结构框架,由一个顶层事务控制着各个层次的事务。顶层事务之下嵌套的事务被称为子事务,其控制每一个局部的变换。

分布式事务(distributed transactions)

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

事务并发遇到的问题

脏写(Dirty Write)

如果一个事务修改了另一个未提交事务修改过的数据,那就意味着发生了脏写 image-20200315224346125 如上图,Session A和Session B各开启了一个事务,Session B中的事务先将number列为1的记录的name列更新为'关羽’, 然后Session A中的事务接着又把这条number列为1的记录的name列更新为张飞。如果之后Session B中的事务进行了回滚, 那么Session A中的更新也将不复存在,这种现象就称之为脏写。

脏读(Dirty Read)

如果一个事务读到了另一个未提交事务修改过的数据,那就意味着发生了脏读 image-20200315224551853 如上图,Session A和Session B各开启了一个事务,Session B中的事务先将number列为1的记录的name列更新为'关羽’, 然后Session A中的事务再去查询这条number为1的记录,如果读到列name的值为'关羽’,而Session B中的事务稍后进行了回滚, 那么Session A中的事务相当于读到了一个不存在的数据,这种现象就称之为脏读。

不可重复读(Non-Repeatable Read)

如果一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后, 该事务都能查询得到最新值,那就意味着发生了不可重复读。其实就是在一个事务里对同一条数据读取多次时,读到的结果不一样。 image-20200315224854489 如上图,我们在Session B中提交了几个隐式事务(注意是隐式事务,意味着语句结束事务就提交了), 这些事务都修改了number列为1的记录的列name的值,每次事务提交之后,如果Session A中的事务都可以查看到最新的值, 这种现象也被称之为不可重复读。

幻读(Phantom)

如果一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时, 能把另一个事务插入的记录也读出来,那就意味着发生了幻读 image-20200315225134020 如上图,Session A中的事务先根据条件number > 0这个条件查询表hero,得到了name列值为'刘备'的记录; 之后Session B中提交了一个隐式事务,该事务向表hero中插入了一条新记录; 之后Session A中的事务再根据相同的条件number > 0查询表hero,得到的结果集中包含Session B中的事务新插入的那条记录,这种现象也被称之为幻读。

隔离级别

读未提交(read uncommitted)

一个事务还没提交时,它做的变更就能被别的事务看到。

读提交(read committed)

一个事务提交后,它做的变更才能被别的事务看到。

可重复读(repeatable read)

一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据一样。当然,未提交的变更其他事务也是看不到的。

串行化(serializable)

对于同一行记录,写会加写锁,读会加读锁,当出现读写锁冲突,后访问的事务必须等前一个事务执行完毕,才能继续执行。

SQL标准中规定,针对不同的隔离级别,并发事务可以发生不同严重程度的问题,具体情况如下:

隔离级别 脏读 不可重复读 幻读
READ UNCOMMITTED Possible Possible Possible
READ COMMITTED Not Possible Possible Possible
REPEATABLE READ Not Possible Not Possible Possible
SERIALIZABLE Not Possible Not Possible Not Possible

设置事务隔离级别

1
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level;

其中的level可选值有4个:

1
2
3
4
5
6
7
level: {
     REPEATABLE READ
   | READ COMMITTED
   | READ UNCOMMITTED
   | SERIALIZABLE
}

查看系统当前的事务隔离级别

1
2
3
4
5
6
mysql> SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+

或者

1
2
3
4
5
6
mysql> SELECT @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ         |
+-------------------------+