数据库事务概述

事务是数据库区别于文件系统的重要特性之一,有了事务就会让数据库始终保持一致性,同时还能通过事务的机制恢复到某个时间点,可以保证已提交到数据库的修改不会因为系统崩溃而丢失。

存储引擎支持情况

SHOW ENGINES命令来查看当前 MySQL 支持的存储引擎都有哪些,以及这些存储引擎是否支持事务。

事务基本概念

事务

数据库事务(transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成

事务处理的原则

保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方 式。当在一个事务中执行多个操作时,要么所有的事务都被提交commit,那么这些修改就永久地保 存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚rollback到最初状态。

事务的ACID特性

ACID特性 说明
原子性 一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。
一致性 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
隔离性 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
持久性 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

事务的状态

事务是一个抽象的概念,它其实对应着一个或多个数据库操作,MySQL根据这些操作所执行的不同阶段把事务大致划分成几个状态:

  • 活动的(active):事务对应的数据库操作正在执行过程中
  • 部分提交的(partially committed): 当事务中的最后一个操作执行完成,但由于操作都在内存中执行,所造成的影响并没有刷新到磁盘时
  • 失败的(failed):当事务处在活动的或者部分提交的状态时,可能遇到了某些错误而无法继续执行,或者人为的停止当前事务的执行
  • 中止的(aborted): 如果事务执行一部分而变为失败的状态,需要把已经修改的事务中的操作还原到事务执 行前的状态。要撤销失败事务对当前数据库造成的影响,当回滚操作执行完毕时数据库恢复到执行事务之前的状态,该事务处在中止的状态
  • 提交的(committed): 当一个处在部分提交的状态的事务将修改过的数据都同步到磁盘上之后,就可以说该事务处在提交的状态

图中可见,只有当事务处于提交的或者中止的状态时,一个事务的生命周期才算是结束。对于已经提交S事务来说,该事务对数据库所做的修改将永久生效,对于处于中止状态的事务,该事务对数据库所做的所有修改都会被回滚到没执行该事务之前的状态。

使用事务

显式事务

步骤1 START TRANSACTION 或者 BEGIN ,作用是显式开启一个事务。

1
2
3
BEGIN;
-- 或者
START TRANSACTION;

START TRANSACTION 语句相较于 BEGIN 特别之处在于:(后边能跟随修饰符)

修饰符 说明
READ ONLY 标识当前事务是一个只读事务,即属于该事务的数据库操作只能读取数据,而不能修改数据。
READ WRITE 标识当前事务是一个读写事务,即属于该事务的数据库操作既可以读取数据,也可以修改数据。
WITH CONSISTENT SNAPSHOT 启动一致性读

步骤2:一系列事务中的操作(主要是DML,不含DDL)

步骤3:提交事务或中止事务(即回滚事务)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 提交事务。当提交事务后,对数据库的修改是永久性的 
COMMIT;

-- 回滚事务。即撤销正在进行的所有没有提交的修改
ROLLBACK;

-- 将事务回滚到某个保存点
ROLLBACK TO [SAVEPOINT]

-- 在事务中创建保存点,方便以后对保持点进行回滚,一个事务可以存在多个保存点
SAVEPOINT 保存点名称;

-- 删除某个保存点
RELEASE SAVEPOINT 保存点名称;

隐式事务

MySQL中有一个系统变量autocommit

1
SHOW VARIABLES LIKE 'autocommit';

默认情况下,如果不显式的使用START TRANSACTION或者BEGIN语句开启一个事务,那么每一条语句都算是一个独立的事务,这种特性称之为事务的自动提交

如果想关闭这种自动提交的功能:

  • 显式的的使用 START TRANSACTION 或者BEGIN语句开启一个事务。这样在本次事务提交或者回 滚前会暂时关闭掉自动提交的功能

  • (或者)把系统变量autocommit的值设置为OFF

    1
    2
    3
    SET autocommit = OFF;
    -- 或者
    SET autocommit = 0;

这样写入的多条语句就算是属于同一个事务,直到我们显式的写出COMMIT语句来把这个事务提交掉,或者显式的写出ROLLBACK语句来把这个事务回滚掉。

注意:

Oracle默认不自动提交,需要手写COMMIT命令,而MySQL默认自动提交。

隐式提交事务的情况

数据定义语言(Data definition language,DDL)

数据库对象,指的就是数据库视图存储过程等结构。当使用CREATEALTERDROP等语句去修改数据库对象时,就会隐式的提交前边语句所属于的事务

1
2
3
4
5
BEGIN;
SELECT ... -- 事务中的一条语句
UPDATE ... -- 事务中的一条语句
... -- 事务中的其它语句
CREATE TABLE ... -- 此语句会隐式的提交前边语句所属于的事务

隐式使用或修改mysql数据库中的表

当使用ALTER USERCREATE USERDROP USERGRANTRENAME USERREVOKESET PASSWORD等语句时也会隐式的提交前边语句所属于的事务

事务控制或关于锁定的语句

  • 当在一个事务还没提交或者回滚时就又使用START TRANSACTION或者BEGIN语句开启另一个事务时,会隐式的提交上一个事务

    1
    2
    3
    4
    5
    6
    7
    BEGIN;

    SELECT ... -- 事务中的一条语句
    UPDATE ... -- 事务中的一条语句
    ... -- 事务中的其它语句

    BEGIN; -- 事务中的其他语句
  • 当前的autocommit系统变量的值为OFF,手动把它调为ON时会隐式的提交前边语句所属的事务。

  • 使用LOCK TABLES 、 UNLOCK TABLES等关于锁定的语句会隐式的提交前边语句所属的事务

加载数据的语句

使用LOAD DATA语句来批量往数据库中导入数据时会隐式的提交前边语句所属的事务。

关于MySQL复制的一些语句

使用START SLAVESTOP SLAVERESET SLAVECHANGE MASTER TO等语句时会隐式的提交前边语句所属的事务。

其它的一些语句

使用ANALYZE TABLECACHE INDEXCHECK TABLEFLUSHLOAD INDEX INTO CACHEOPTIMIZE TABLEREPAIR TABLERESET等语句也会隐式的提交前边语句所属的事务。

事务隔离级别

数据并发问题

并发问题 说明
脏写 Dirty Write 对于两个事务T1、T2,T1修改了另一个未提交的事务T2修改过的数据
脏读 Dirty Read 对于两个事务T1、T2,T1读取了已经被T2更新但还没有被提交的字段。之后, 若 T2 回滚,T1读取的内容就是临时且无效的
不可重复读 Non-Repeatable Read 对于两个事务T1、T2,T1读取了一个字段, 然后 T2 更新了该字段。之后,T1再次读取同一个字段,值就不同了
幻读 Phantom 对于两个事务T1、T2,T1从一个表中读取了一个字段,然后T2在该表中插入了一些新的行。之后,如果T1再次读取同一个表,就会多出几行

事务隔离级别

脏写

脏读

不可重复读

幻读

隔离级别 说明
READ UNCOMMITED(读未提交) 允许事务读取其他事务未提交的变更,会出现脏读、不可重复读、幻读的问题
READ COMMITTED(读已提交) 只允许事务读取已经被其他事务提交的变更,可以避免脏读,但存在不可重复读、幻读的问题
REPETABLE READ(可重复读) 确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新,避免了脏读和不可重复读,但存在幻读问题
SERIALIZABLE(串行化) 确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新、删除的操作,解决所有的并发问题,但是性能低下

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

隔离级别 脏读可能性 不可重复读可能性 幻读可能性 加锁读可能性
READ UNCOMMITED Y Y Y N
READ COMMITTED N Y Y N
REPETABLE READ N N Y N
SERIALIZABLE N N N Y

不同的隔离级别有不同的现象,并有不同的锁和并发机制,隔离级别越高,数据库的并发性能就越差,4 种事务隔离级别与并发性能的关系如下:

MySQL 支持的四种隔离级别

MySQL的默认隔离级别为REPEATABLE READ,可以手动修改一下事务的隔离级别

1
2
3
4
5
6
7
8
-- 查看隔离级别,MySQL 5.7.20的版本之前:
SHOW VARIABLES LIKE 'tx_isolation';

-- 查看隔离级别,MySQL 5.7.20版本之后
SHOW VARIABLES LIKE 'transaction_isolation';

-- 不同MySQL版本中都可以使用的:
SELECT @@transaction_isolation

设置事务的隔离级别

修改事务的隔离级别:

1
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL;
  • READ UNCOMMITTED

  • READ COMMITTED

  • REPEATABLE READ

  • SERIALIZABLE

1
SET [GLOBAL|SESSION] TRANSACTION_ISOLATION = '隔离级别';
  • READ-UNCOMMITTED
  • READ-COMMITTED
  • REPEATABLE-READ

  • SERIALIZABLE

关于设置时使用GLOBAL或SESSION的影响

使用GLOBAL关键字(在全局范围影响):

1
2
3
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- 或
SET GLOBAL TRANSACTION_ISOLATION = 'SERIALIZABLE';
  • 当前已经存在的会话无效
  • 只对执行完该语句之后产生的会话起作用

    使用SESSION关键字(在会话范围影响):

1
2
3
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- 或
SET SESSION TRANSACTION_ISOLATION = 'SERIALIZABLE';
  • 对当前会话的所有后续的事务有效
  • 如果在事务之间执行,则对后续的事务有效
  • 该语句可以在已经开启的事务中间执行,但不会影响当前正在执行的事务

如果在服务器启动时想改变事务的默认隔离级别,可以修改启动参数transaction_isolation的值。

数据库规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性 就越好,但并发性越弱。

事务的常见分类

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