前言

在网上的大部分资料中,对于事务及Mysql的事务隔离级别都只是给出了基本概念和定义以及例子,并没有针对数据库进行实际的操作,这篇文章通过几个简单的操作实例来对事务进行操作,让各位对事务有更加深刻的认识。

在阅读本文章之前,需要对数据库的事务有基本的认识,可以参考我之前的文章
数据库事务的基本概念

前置准备

查看数据库是否开启了自动提交

mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set, 1 warning (0.00 sec)

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

关闭数据库自动提交

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

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

mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | OFF   |
+---------------+-------+
1 row in set, 1 warning (0.00 sec)
  • 值为 0 或值为 OFF:关闭事务自动提交。如果关闭自动提交,用户将会一直处于某个事务中,只有提交或回滚后才会结束当前事务,重新开始一个新事务。
  • 值为 1 或值为 ON:开启事务自动提交。如果开启自动提交,则每执行一条 SQL 语句,事务都会提交一次。

在我们的实验过程中,如果没有关闭自动提交,观察会比较困难,需要显式地开启事务,所以这边建议是关闭自动提交。

读未提交(Read Uncommitted)

  • 设置当前会话事务隔离级别(事务A)
mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)

mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation   |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)
  • 设置当前会话事务隔离级别(事务B),操作同上

image.png

测试:

事务A修改id为1的用户的年龄为20,同时事务B去查询,注意:此时事务A并没有提交事务
image.png

从结果可以看出,事务B(右边)读取到了事务A(左边)未提交的事务的修改。也就是发生了脏读。

将事务A回滚,事务B再次查询
image.png

可以看出,当事务A回滚后,事务B又读取到了id为1的用户的年龄10,同时印证上面发生了脏读。

读已提交 (Read Committed)

先将两个会话的事务隔离级别都设置为读未提交
image.png

事务A,向张三转账100元,事务B在转账前后进行查询张三的账户余额,结果如下:
image.png
此时可以看出,事务B(右边)并没有查询到张三的账户余额变多了,此时,我们将事务A进行提交,事务B再次进行查询。结果如下:
image.png
可以看到,事务A提交后,事务B查询到了转账之后的数据。这就是读已提交。读已提交解决了脏读的问题。同时,事务B三次查询到的张三的账户余额都不相同,这就是不可重复读的问题。

可重复读(Repeatable Read)

先将两个会话的事务隔离级别都设置为可重复读
image.png

此时将李四的账户转出100,事务B进行查询
image.png

可以看出,与读已提交一样,事务B查询到的数据是一样的,李四的余额一直是200,此时我们将事务A提交。
image.png

可以看出,与读已提交不同的是,在事务A提交之后,事务B查询到的还是200,也就是说,可重复读的隔离级别解决了不可重复读的问题。

串行化(Serializable)

这次我们先保持可重复读的事务隔离级别,演示一下幻读发生的场景。
事务B向ID大于等于1的所有用户增加20元,但是在执行增加之前,事务A新增了一个用户并提交了事务,此时如下图所示:
image.png

可以看出,事务B原本只想更新两个用户的余额,但事务A添加了一个用户并提交了,那么事务B就更新了三条记录,并且查询也会出现三条记录。
image.png

此时,我们将事务的隔离级别修改为串行。
image.png

重复上面的步骤,我们将添加修改为删除
image.png

可以看到,当出现这种情况时,事务A在删除时会阻塞,因为事务B进行了查询,当事务B又想进行修改时,mysql会提示说如果你想获得这个锁,将会造成死锁,并回滚事务。

此时,如果想要删除wangwu,需要将事务B先提交,下图为事务B未提交时
image.png
提交事务B之后:
image.png

而此时事务A未提交,事务B进行查询时会出现事务B阻塞:
image.png
提交事务A之后,事务B能够查询到
image.png

以上。