保证数据的一致性状态的MySQL事务

当多个用户访问同一数据时,一个用户在更改数据的过程中可能有其它用户同时发起更改请求,为保证数据的一致性状态,MySQL 引入了事务。

本文首先介绍了事务控制语句和隔离级别,然后介绍了字符集和校对规则的相关概念和操作。

为什么说一定要开启事务后才能对数据进行操作?

在银行业务中,有一条记账原则,即有借有贷,借贷相等。为了保证这种原则,每发生一笔银行业务,就必须确保会计账目上借方科目和贷方科目至少各记一笔,并且这两笔账要么同时成功,要么同时失败。如果出现只记录了借方科目,或者只记录了贷方科目的情况,就违反了记账原则。会出现记错账的情况。

在银行的日常业务中,只要是同一银行(如都是中国农业银行,简称农行),一般都支持账户间的直接转账。因此,银行转账操作往往会涉及两个或两个以上的账户。在转出账户的存款减少一定金额的同时,转入账户的存款就要增加相应的金额。

下面,在 MySQL 数据库中模拟一下上述提及的转账问题。

假如要从张三的账户直接转账 500 元到李四的账户。首先需要创建账户表,存放用户张三和李四的账户信息。创建账户表和插入数据的 SQL 语句和运行结果如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> CREATE DATABASE mybank;
Query OK, 1 row affected (0.02 sec)
mysql> USE mybank;
Database changed
mysql> CREATE TABLE bank(
-> customerName VARCHAR(20), #用户名
-> currentMoney DECIMAL(10,2) #当前余额
-> )ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.26 sec)

mysql> INSERT INTO bank (customerName,currentMoney) VALUES('张三',1000);;
Query OK, 1 row affected (0.07 sec)

mysql> INSERT INTO bank (customerName,currentMoney) VALUES('李四',1);
Query OK, 1 row affected (0.08 sec)

查询 bank 数据表的 SQL 语句和运行结果如下:

1
2
3
4
5
6
7
8
mysql> SELECT * FROM bank;
+--------------+--------------+
| customerName | currentMoney |
+--------------+--------------+
| 张三 | 1000.00 |
| 李四 | 1.00 |
+--------------+--------------+
2 rows in set (0.02 sec)

结果显示,张三和李四两个账户的余额总和为 1000+1=1001 元。

下面开始模拟实现转账功能。从张三的账户直接转账 500 元到李四的账户,可以使用 UPDATE 语句分别修改张三的账户和李四的账户。张三的账户减少 500 元,李四的账户增加 500 元, SQL 语句如下所示:

1
2
3
4
/*转账测试:张三转账给李四 500 元*/
# 张三的账户少 500 元,李四的账户多 500
UPDATE bank SET currentMoney = currentMoney-500 WHERE customerName = '张三';
UPDATE bank SET currentMoney = currentMoney+500 WHERE customerName = '李四';

正常情况下,执行以上的转账操作后,余额总和应保持不变,仍为 1001 元。但是,如果在这个过程的其中一个环节出现差错,如在张三的账户减少 500 元之后,这时发生了服务器故障,李四的账户没有立即增加 500 元,此时,第三方读取到两个账户的余额总和变为 500+1=501 元,即账户总额间少了 500 元。

MySQL 为了解决此类问题,提供了事务。事务可以将一系列的数据操作捆绑成一个整体进行统一管理,如果某一事务执行成功,则在该事务中进行的所有数据更改均会提交,成为数据库中的永久组成部分。如果事务执行时遇到错误,则就必须取消或回滚。取消或回滚后,数据将全部恢复到操作前的状态,所有数据的更改均被清除。

MySQL 通过事务保证了数据的一致性。上述提到的转账过程就是一个事务,它需要两条 UPDATE 语句来完成。这两条语句是一个整体,如果其中任何一个环节出现问题,则整个转账业务也应取消,两个账户中的余额应恢复为原来的数据,从而确保转账前和转账后的余额总和不变,即都是 1001 元。

数据库事务的概念和特性

数据库的事务(Transaction)是一种机制、一个操作序列,包含了一组数据库操作命令。事务把所有的命令作为一个整体一起向系统提交或撤销操作请求,即这一组数据库命令要么都执行,要么都不执行,因此事务是一个不可分割的工作逻辑单元。

在数据库系统上执行并发操作时,事务是作为最小的控制单元来使用的,特别适用于多用户同时操作的数据库系统。例如,航空公司的订票系统、银行、保险公司以及证券交易系统等。

事务具有 4 个特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),这 4 个特性通常简称为 ACID。

  • 原子性:事务是一个完整的操作。事务的各元素是不可分的(原子的)。事务中的所有元素必须作为一个整体提交或回滚。如果事务中的任何元素失败,则整个事务将失败。以银行转账事务为例,如果该事务提交了,则这两个账户的数据将会更新。如果由于某种原因,事务在成功更新这两个账户之前终止了,则不会更新这两个账户的余额,并且会撤销对任何账户余额的修改,事务不能部分提交。
  • 一致性:当事务完成时,数据必须处于一致状态。也就是说,在事务开始之前,数据库中存储的数据处于一致状态。在正在进行的事务中. 数据可能处于不一致的状态,如数据可能有部分被修改。然而,当事务成功完成时,数据必须再次回到已知的一致状态。通过事务对数据所做的修改不能损坏数据,或者说事务不能使数据存储处于不稳定的状态。以银行转账事务事务为例。在事务开始之前,所有账户余额的总额处于一致状态。在事务进行的过程中,一个账户余额减少了,而另一个账户余额尚未修改。因此,所有账户余额的总额处于不一致状态。事务完成以后,账户余额的总额再次恢复到一致状态。
  • 隔离性:对数据进行修改的所有并发事务是彼此隔离的,这表明事务必须是独立的,它不应以任何方式依赖于或影响其他事务。修改数据的事务可以在另一个使用相同数据的事务开始之前访问这些数据,或者在另一个使用相同数据的事务结束之后访问这些数据。另外,当事务修改数据时,如果任何其他进程正在同时使用相同的数据,则直到该事务成功提交之后,对数据的修改才能生效。张三和李四之间的转账与王五和赵二之间的转账,永远是相互独立的。
  • 持久性:事务的持久性指不管系统是否发生了故障,事务处理的结果都是永久的。一个事务成功完成之后,它对数据库所作的改变是永久性的,即使系统出现故障也是如此。也就是说,一旦事务被提交,事务对数据所做的任何变动都会被永久地保留在数据库中。事务的 ACID 原则保证了一个事务或者成功提交,或者失败回滚,二者必居其一。因此,它对事务的修改具有可恢复性。即当事务失败时,它对数据的修改都会恢复到该事务执行前的状态。

MySQL执行事务的语法和流程

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

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

为了维护 MySQL 服务器,经常需要在 MySQL 数据库中进行日志操作:

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

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

执行事务的语法和流程如下所示:
SQL 使用下列语句来管理事务。

1
2
3
4
// 开始事务
BEGIN;
//
START TRANSACTION;

MySQL设置事务自动提交(开启和关闭)

MySQL 默认开启事务自动提交模式,即除非显式的开启事务(BEGIN 或 START TRANSACTION),否则每条 SOL 语句都会被当做一个单独的事务自动执行。但有些情况下,我们需要关闭事务自动提交来保证数据的一致性。下面主要介绍如何设置事务自动提交模式。

在 MySQL 中,可以通过 SHOW VARIABLES 语句查看当前事务自动提交模式,如下所示:

1
2
3
4
5
6
7
mysql> SHOW VARIABLES LIKE 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set, 1 warning (0.04 sec)

结果显示,autocommit 的值是 ON,表示系统开启自动提交模式。

在 MySQL 中,可以使用 SET autocommit 语句设置事务的自动提交模式,语法格式如下:

1
SET autocommit = 0|1|ON|OFF;

对取值的说明:

  • 值为 0 和值为 OFF:关闭事务自动提交。如果关闭自动提交,用户将会一直处于某个事务中,只有提交或回滚后才会结束当前事务,重新开始一个新事务。
  • 值为 1 和值为 ON:开启事务自动提交。如果开启自动提交,则每执行一条 SQL 语句,事务都会提交一次。

示例
下面我们关闭事务自动提交,模拟银行转账。

使用 SET autocommit 语句关闭事务自动提交,且张三转给李四 500 元,SQL 语句和运行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mysql> SET autocommit = 0;                                                      ;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM mybank.bank;
+--------------+--------------+
| customerName | currentMoney |
+--------------+--------------+
| 张三 | 1000.00 |
| 李四 | 1.00 |
+--------------+--------------+
2 rows in set (0.00 sec)
mysql> UPDATE bank SET currentMoney = currentMoney-500 WHERE customerName='张三' ;
Query OK, 1 row affected (0.02 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> UPDATE bank SET currentMoney = currentMoney+500 WHERE customerName='李四';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0

这时重新打开一个 cmd 窗口,查看 bank 数据表中张三和李四的余额,SQL 语句和运行结果如下所示:

1
2
3
4
5
6
7
8
mysql> SELECT * FROM mybank.bank;
+--------------+--------------+
| customerName | currentMoney |
+--------------+--------------+
| 张三 | 1000.00 |
| 李四 | 1.00 |
+--------------+--------------+
2 rows in set (0.00 sec)

结果显示,张三和李四的余额是事务执行前的数据。

下面在之前的窗口中使用 COMMIT 语句提交事务,并查询 bank 数据表的数据,如下所示:

1
2
3
4
5
6
7
8
9
10
mysql> COMMIT;
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)

结果显示,bank 数据表的数据更新成功。

在本例中,关闭自动提交后,该位置会作为一个事务起点,直到执行 COMMIT 语句和 ROLLBACK 语句后,该事务才结束。结束之后,这就是下一个事务的起点。

关闭自动提交功能后,只用当执行 COMMIT 命令后,MySQL 才将数据表中的资料提交到数据库中。如果执行 ROLLBACK 命令,数据将会被回滚。如果不提交事务,而终止 MySQL 会话,数据库将会自动执行回滚操作。

使用 BEGIN 或 START TRANSACTION 开启一个事务之后,自动提交将保持禁用状态,直到使用 COMMIT 或 ROLLBACK 结束事务。之后,自动提交模式会恢复到之前的状态,即如果 BEGIN 前 autocommit = 1,则完成本次事务后 autocommit 还是 1。如果 BEGIN 前 autocommit = 0,则完成本次事务后 autocommit 还是 0。

参考链接

保证数据的一致性状态的MySQL事务

https://blog.jiejaitt.top/posts/568a927b.html

作者

JIeJaitt

发布于

2021-06-22

更新于

2023-07-31

许可协议

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×