MySQL执行事务的语法和流程

MySQL 提供了多种存储引擎来支持事务。支持事务的存储引擎有 InnoDB 和 BDB,其中,InnoDB 存储引擎事务主要通过 UNDO 日志和 REDO 日志实现,MyISAM 存储引擎不支持事务。

拓展:任何一种数据库,都会拥有各种各样的日志,用来记录数据库的运行情况、日常操作、错误信息等,MySQL 也不例外。例如,当用户 root 登录到 MySQL 服务器,就会在日志文件里记录该用户的登录时间、执行操作等。

为了维护 MySQL 服务器,经常需要在 MySQL 数据库中进行日志操作:
  • UNDO 日志:复制事务执行前的数据,用于在事务发生异常时回滚数据。
  • REDO 日志:记录在事务执行中,每条对数据进行更新的操作,当事务提交时,该内容将被刷新到磁盘。

默认设置下,每条 SQL 语句就是一个事务,即执行 SQL 语句后自动提交。为了达到将几个操作做为一个整体的目的,需要使用 BEGIN 或 START TRANSACTION 开启一个事务,或者禁止当前会话的自动提交。

关于事务的自动提交可阅读学习《MySQL设置事务自动提交》一节。

执行事务的语法和流程

SQL 使用下列语句来管理事务。

1) 开始事务

BEGIN;

START TRANSACTION;

这个语句显式地标记一个事务的起始点。

2) 提交事务

MySQL 使用下面的语句来提交事务:

COMMIT;

COMMIT 表示提交事务,即提交事务的所有操作,具体地说,就是将事务中所有对数据库的更新都写到磁盘上的物理数据库中,事务正常结束。

提交事务,意味着将事务开始以来所执行的所有数据都修改成为数据库的永久部分,因此也标志着一个事务的结束。一旦执行了该命令,将不能回滚事务。只有在所有修改都准备好提交给数据库时,才执行这一操作。

3) 回滚(撤销)事务

MySQL 使用以下语句回滚事务:

ROLLBACK;

ROLLBACK 表示撤销事务,即在事务运行的过程中发生了某种故障,事务不能继续执行,系统将事务中对数据库的所有已完成的操作全部撤销,回滚到事务开始时的状态。这里的操作指对数据库的更新操作。

当事务执行过程中遇到错误时,使用 ROLLBACK 语句使事务回滚到起点或指定的保持点处。同时,系统将清除自事务起点或到某个保存点所做的所有的数据修改,并且释放由事务控制的资源。因此,这条语句也标志着事务的结束。

总结

BEGIN 或 START TRANSACTION 语句后面的 SQL 语句对数据库数据的更新操作都将记录在事务日志中,直至遇到 ROLLBACK 语句或 COMMIT 语句。如果事务中某一操作失败且执行了 ROLLBACK 语句,那么在开启事务语句之后所有更新的数据都能回滚到事务开始前的状态。如果事务中的所有操作都全部正确完成,并且使用了 COMMIT 语句向数据库提交更新数据,则此时的数据又处在新的一致状态。

实例演示

下面通过两个例子来演示一下 MySQL 事务的具体用法。

示例 1

下面模拟在张三的账户减少 500 元后,李四的账户还未增加 500 时,有其他会话访问数据表的场景。由于代码需要在两个窗口中执行,为了方便阅读,这里我们称为 A 窗口和 B 窗口。

1) 在 A 窗口中开启一个事务,并更新 mybank 数据库中 bank 表的数据,SQL 语句和运行结果如下:
mysql> USE mybank;
Database changed
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE bank SET currentMoney = currentMoney-500
    -> WHERE customerName='张三';
Query OK, 1 row affected (0.05 sec)
Rows matched: 1  Changed: 1  Warnings: 0

2) 在 B 窗口中查询 bank 数据表中的数据,SQL 语句和运行结果如下:
mysql> SELECT * FROM mybank.bank;
+--------------+--------------+
| customerName | currentMoney |
+--------------+--------------+
| 张三         |      1000.00 |
| 李四         |         1.00 |
+--------------+--------------+
2 rows in set (0.00 sec)
从结果可以看出,虽然 A 窗口中的事务已经更改了 bank 表中的数据,但没有立即更新数据,这时其他会话读取到的仍然是更新前的数据。

3) 在 A 窗口中继续执行事务并提交事务,SQL 语句和运行结果如下:
mysql> UPDATE bank SET currentMoney = currentMoney+500
    -> WHERE customerName='李四';
Query OK, 1 row affected (0.05 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> COMMIT;
Query OK, 0 rows affected (0.07 sec)

4) 在 B 窗口中再次查询 bank 数据表的数据,SQL 语句和运行结果如下:
mysql> SELECT * FROM mybank.bank;
+--------------+--------------+
| customerName | currentMoney |
+--------------+--------------+
| 张三         |       500.00 |
| 李四         |       501.00 |
+--------------+--------------+
2 rows in set (0.00 sec)
在 A 窗口中执行 COMMIT 提交事务后,对数据所做的更新将一起提交,其他会话读取到的是更新后的数据。从结果可以看出张三和李四的总账户余额和转账前保持一致,这样数据从一个一致性状态更新到另一个一致性状态。

前面提到,当事务在执行中出现问题,也就是不能按正常的流程执行一个完整的事务时,可以使用 ROLLBACK 语句进行回滚,使用数据恢复到初始状态。

在例 1 中,张三的账户余额已经减少到 500 元,如果再转出 1000 元,将会出现余额为负数,因此需要回滚到原始状态。如例 2 所示。

示例 2

将张三的账户余额减少 1000 元,并让事务回滚,SQL 语句和运行结果如下所示:
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
 
mysql> UPDATE bank SET currentMoney = currentMoney-1000 WHERE customerName='张三';
Query OK, 1 row affected (0.04 sec)
Rows matched: 1  Changed: 1  Warnings: 0
 
mysql> ROLLBACK;
Query OK, 0 rows affected (0.07 sec)
 
mysql> SELECT * FROM mybank.bank;
+--------------+--------------+
| customerName | currentMoney |
+--------------+--------------+
| 张三         |       500.00 |
| 李四         |       501.00 |
+--------------+--------------+
2 rows in set (0.00 sec)
从结果可以看出,执行事务回滚后,账户数据恢复到初始状态,即该事务执行之前的状态。

拓展

在数据库操作中,为了有效保证并发读取数据的正确性,提出了事务的隔离级别。在例 1 和例 2 的演示中,事务的隔离级别为默认隔离级别。在 MySQL 中,事务的默认隔离级别是 REPEATABLE-READ (可重读)隔离级别,即事务未结束时(未执行 COMMIT 或 ROLLBACK),其它会话只能读取到未提交数据。

请猛击《MySQL事务隔离级别》了解更多内容。

注意事项

MySQL 事务是一项非常消耗资源的功能,大家在使用过程中要注意以下几点。

1) 事务尽可能简短

事务的开启到结束会在数据库管理系统中保留大量资源,以保证事务的原子性、一致性、隔离性和持久性。如果在多用户系统中,较大的事务将会占用系统的大量资源,使得系统不堪重负,会影响软件的运行性能,甚至导致系统崩溃。

2) 事务中访问的数据量尽量最少

当并发执行事务处理时,事务操作的数据量越少,事务之间对相同数据的操作就越少。

3) 查询数据时尽量不要使用事务

对数据进行浏览查询操作并不会更新数据库的数据,因此应尽量不使用事务查询数据,避免占用过量的系统资源。

4) 在事务处理过程中尽量不要出现等待用户输入的操作

在处理事务的过程中,如果需要等待用户输入数据,那么事务会长时间地占用资源,有可能造成系统阻塞。