MySQL 事务

多表关联的情况下,一条新记录的生成往往涉及多张表的操作。

一个典型的场景,银行转帐。要完成 A 转帐到 B,

  • 从 A 帐户减去相应金额
  • 给 B 帐户加上相应金额。

这两步要么一起成功,要么都失败,否则就会造成数据不一致。比如 A 的钱少了,但 B 的钱没增加,或者 A 的扣款失败,B 的钱也增加了。

所以需要一种机制来保证这一操作过程中每一步的正确性,当其中任意操作失败时应该将已经进行过的操作回滚,保证整体都失败。

此时这些被绑定的一连串操作便形成了 事务

下面创建一张空表,为后面示例作准备。

mysql> CREATE TABLE test(idx int);
Query OK, 0 rows affected (0.02 sec)

事务的语法

  • MySQL 中,通过 START TRANSACTION 语句来开始一个事务,也可以使用别名 BEGINBEGIN WORK 语句。
  • COMMIT 语句提交修改。
  • 通过 ROLLBACK 语句回滚。

其中 COMMITROLLBACK 均可用来结束一个事务。

来看一个简单的示例:

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO test values(1);
Query OK, 1 row affected (0.01 sec)

mysql> INSERT INTO test values(2);
Query OK, 1 row affected (0.00 sec)

mysql> SELECT * FROM test;
+------+
| idx  |
+------+
|    1 |
|    2 |
+------+
2 rows in set (0.00 sec)

mysql> ROLLBACK;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM test;
Empty set (0.00 sec)

上面创建了一个名为 test 的表,开始事务,向其中分两次插入记录。

然后查询刚刚插入的两条记录。因为此时是处于一个事务中,所以这两条记录实际上是可以被回滚的。

调用 ROLLBACK 后两次查询,表中已经没有了刚刚的两条记录。

autocommit

抛开事务,MySQL 默认情况下开启了一个自动提交的模式 autocommit,一条语句被回车执行后该语句便生效了,变更会保存在 MySQL 的文件中,无法撤消。当使用相应语句比如 BEGIN 显式声明开始一个事务时,autocommit 默认会是关闭状态。

可通过查询 @@autocommit 变量来查看当前是否是自动提交的状态,

mysql> SELECT @@autocommit;
+--------------+
| @@autocommit |
+--------------+
|            1 |
+--------------+
1 row in set (0.00 sec)

通过设置 autocommit0 可关闭自动提交,

SET autocommit = 0;

无论是否是自动提交模式,语句执行后都会生效,区别在于,非自动模式下,没提交的那些操作是可以回滚的,一旦提交后便不可撤消了。换句话说,当 autocommit 关闭时,一直是处于事务操作中的,可随时调用 ROLLBACK 进行回滚。

下面的语句展示了 autocommit 关闭时 COMMIT 与否的影响,

mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
|            1 |
+--------------+
1 row in set (0.00 sec)

mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test;
Empty set (0.00 sec)

mysql> insert into test values(1);
Query OK, 1 row affected (0.00 sec)

mysql> insert into test values(2);
Query OK, 1 row affected (0.00 sec)

mysql> select * from test;
+------+
| idx  |
+------+
|    1 |
|    2 |
+------+
2 rows in set (0.00 sec)

mysql> rollback;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test;
Empty set (0.00 sec)

mysql> insert into test values(3);
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> rollback;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test;
+------+
| idx  |
+------+
|    3 |
+------+
1 row in set (0.00 sec)

事务中会触发隐式提交的操作

虽说事务模式下关闭了 autocommit 必需手动执行 COMMIT 才能提交,但有些语句和操作是会隐式触发提交的,在进行事务过程中需要注意,这些操作可在官方文档 Statements That Cause an Implicit Commit 中查找到。

所以不能单纯地认为一个事务中所有操作都是绝对安全可回滚的。

Node.js 示例

以下是来自 npm 模块 mysqljs/mysql 关于事务的示例:

connection.beginTransaction(function(err) {
  if (err) { throw err; }
  connection.query('INSERT INTO posts SET title=?', title, function (error, results, fields) {
    if (error) {
      return connection.rollback(function() {
        throw error;
      });
    }

    var log = 'Post ' + results.insertId + ' added';

    connection.query('INSERT INTO log SET data=?', log, function (error, results, fields) {
      if (error) {
        return connection.rollback(function() {
          throw error;
        });
      }
      connection.commit(function(err) {
        if (err) {
          return connection.rollback(function() {
            throw err;
          });
        }
        console.log('success!');
      });
    });
  });
});

总结

关于 autocommit 与事务,他们其实是这样的关系:

  • MySQL 每一步操作都可看成一个原子操作。
  • 默认情况下,autocommit 是开启状态,所以第一条语句都是执行后自动提交,语句产生的效果被记录保存了下来。
  • 关于 autocommit 后,语句不会自动提交,需要手动调用 COMMIT 来让效果被持久化。
  • 也可通过 BEGIN 开始一个事务,此时 autocommit 隐式地被关闭了,因此事务操作过程中也是需要显式调用 COMMIT 来让效果永久生效。
  • BEGIN 开启事务后,使用 COMMITROLLBACK 来结束该事务。事务结束后 autocommit 回到原有的状态。

所以,autocommit 这个开关相当于一个记录的事务标记,它被关闭时你一直处于一个可回滚的状态。而 BEGIN 开启的是一次临时事务,一旦 COMMITROLLBACK 本次事务便结束了。

相关资源