事务作为数据库的一项重要特性,这里我们简单介绍下什么是事务,以及该如何使用
概述
定义
MySQL的操作使用相信大家都不陌生,即是通过一条条的SQL语句去完成的。而如果我们将一条或多条对数据库操作的SQL语句视作一个整体来看,即是所谓的Transaction事务。对于事务而言,其需要满足四个特性——ACID
Note
- 对于MySQL数据库而言,并不是所有的存储引擎都支持事务。具体地,对于InnoDB、NDB存储引擎而言,其是支持事务的;但对于MyISAM存储引擎而言,是不支持事务的
ACID
Atomicity 原子性
事务的Atomicity原子性指的是,对于一个事务而言其是执行的最小单位,不可以被分割。具体地,对于一个事务中的若干条SQL语句而言,要么这些SQL语句全部不执行,要么这些SQL语句全部执行。而不允许在一个事务中出现只执行一部分SQL语句而另一部分SQL语句未执行的情况。在InnoDB引擎中,原子性是通过undo log回滚日志保证的
这里我们以A给B的账户转账10元为例进行说明。对于这个转账操作来说,我们可以大致分为两个步骤
- 将A账户余额减去10
- 将B账户余额增加10
显然上述两个步骤要么全部执行了,要么全部不执行了。而其它任何的执行情况来说,即只执行步骤1或只执行步骤2,显然都是错误的
Consistency 一致性
数据库始终是对真实的现实世界进行描述的。故如果数据库中的数据能够满足现实世界的约束,即认为是符合Consistency一致性的。这里关于约束、规则的定义是主观的,比如上面转账的例子中,如果我们可以将 A、B两人账户的总额加在一起是不变 作为约束。还可以根据实际业务需要,比如根据账户类型借记卡还是信用卡,来考虑是否将 账户余额不允许为负值 作为约束。事实上对于MySQL而言,其语法也在一定程度上支持约束。比如NOT NULL非空约束
Isolation 隔离性
Isolation隔离性指的是多个事务并发执行时不应相互影响。总体上,在InnoDB引擎中,Read Committed已提交读、Repeatable Read可重复读隔离级别通过MVCC多版本并发控制机制实现,Serializable串行化隔离级别则通过锁机制实现的。进一步地,MVCC机制则是通过undo log回滚日志实现的
同样还是以A给B转账为例。不同的是,这里假设两次转账操作是并发的。可以看到当两个转账事务按如下方式交错执行时,即会出现一些意外的情况
A给B转了两次账,共计20元。结果B的账户上是多了20元,可是A的账户上却只少了10元。这种局面显然是不应该出现的
Durability 持久性
所谓Durability持久性是指当事务完成后对数据的修改应该落到磁盘中,即被持久化存储。在InnoDB引擎中,持久性是通过redo log重做日志保证的
状态
对于事务一个而言,其通常存在下面的几种状态
- 活动状态
正在执行该事务中的数据库操作
- 部分提交状态
该事务中的数据库操作虽然全部执行完了。但此时均只是发生在内存中,还未刷新同步到硬盘
- 提交状态
处于部分提交状态的事务将数据同步刷新到硬盘
- 失败状态
对处于活动状态、部分提交状态的事务而言,如果发生意外(数据库出现错误、断电等)或主动停止当前事务,则该事务处于失败状态
- 中止状态
根据事务的原子性可知,当事务处于失败状态时需要通过Rollback回滚将数据库数据恢复到执行事务之前的状态。当Rollback回滚操作完成后,该事务即为中止状态
上述各状态之间的转换关系,如下图所示
基本使用
开启事务
下面语句均可开启一个事务。其中对于start transaction语句而言,其后面还可使用修饰符,若同时使用多个修饰符需通过逗号进行分隔。具体地,修饰符有以下几种:
- READ ONLY:只读事务
- READ WRITE:读写事务
- WITH CONSISTENT SNAPSHOT:一致性读
如果不使用修饰符则默认为读写事务。值得一提的是,不可同时使用READ ONLY、READ WRITE修饰符
1 | -- 开启事务 |
提交事务
通过下面的SQL语句即可提交事务
1 | -- 提交事务 |
下图即为一个事务操作的基本流程,可以看到该事务中间只含有一条数据库操作。事实上,在事务中可以包含若干条数据库操作语句
中止事务
在事务中,如果发现某个SQL语句输入错误了。可以通过下面的语句进行回滚来中止事务
1 | rollback; |
下图即为一个事务中途中止的示例。可以看到通过rollback回滚操作,即可将数据库数据恢复到该事务开始之前的状态。这里我们演示的是主动中止事务的情况。事实上,当事务在执行过程中发生了错误而无法继续执行时,事务会自动进行回滚
保存点
在上面的例子中,我们在事务执行时由于手抖了把SQL语句中的 “电气工程” 写成 “电器工程”,就不得不进行全部回滚。然后又要从头执行事务中所有数据库操作,此举显然很麻烦。为此savepoint保存点应运而生,我们可以在事务执行过程中适当建立保存点。这样后续如果需要回滚就不必回滚到事务开启前最初的状态,而只需回滚到指定保存点所在的状态即可
保存点相关的SQL语句如下所示
1 | -- 建立保存点 |
下图即生动形象地反应了关于保存点的使用姿势
自动提交
所谓自动提交指的是,如果我们不显式地通过 start transaction 或 begin 开启事务的话,则对于每条SQL语句而言均视为一个事务进行执行。该特性可通过系统变量autocommit进行使能控制,其默认为on。具体可通过下面的SQL语句查看、修改该系统变量
1 | -- 查看系统变量 autocommit |
如果我们不想使用自动提交功能的话,具体有以下两种途径实现
- 关闭autocommit自动提交
将系统变量autocommit修改为off以关闭自动提交功能。这样数据库会将多条SQL语句视为在同一个事务当中,直到我们显式地使用 commit提交事务 或 rollback回滚事务
- 显式地通过start transaction 或 begin开启事务
当我们通过显式地通过start transaction 或 begin开启事务时,即使系统变量autocommit为on。自动提交的功能也会暂时被关闭。直到该事务被commit提交 或 rollback回滚
隐式提交
所谓隐式提交指的是,当我们通过显式地通过start transaction、begin开启事务 或 关闭了自动提交功能 后,如果遇到特殊类型的语句时既会导致当前事务被提交,效果等同于commit语句。这里就会常见的导致隐式提交的操作类型介绍下:
- DDL类型的SQL语句。例如create、alter、drop等语句
- 使用锁控制相关的语句。例如lock tables、unlock tables等语句
- MySQL复制的语句。例如{start|stop|reset} slave、change master to等语句
- 使用、修改mysql库中的表。例如{alter|create|drop|rename} user、grant、set password等语句
- 当前事务还未提交或回滚,就又通过start transaction、begin开启一个新的事务。则上一个事务会被提交
- 加载数据的操作。例如批量导入load data语句
参考文献
- MySQL是怎样运行的