MySQL事务的问题:脏读、幻读、不可重复读
在上一篇文章中,我们已经学习过了事务相关的基础知识,今天,我们继续学习事务有可能带来的一些问题。其实在一次请求和连接中,事务是不会出现什么问题的,毕竟在一个事务中,要么全提交,要么全回滚。但是如果有多个客户端连接,也就是说在并发操作事务的情况下,就会发生各种问题。其中最典型的就是下面三种情况,不过首先我们设置一下事务隔离级别,这个东西我们下次再讲,这回我们先把事务隔离级别设置为最低的级别。
go
-- my.cnf
[server]
transaction-isolation = READ-UNCOMMITTED
脏读
脏读的意思就是两个事务同时在运行,其中 A 事务修改了某个字段,B 事务读取了这个字段,这时可能因为某种原因,A 事务的修改操作回滚了,那么 B 读取的数据就是不正确的,也就是说,B 读到的数据是 "脏" 的。
go
-- 事务 A
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update test_user set username = 'aaa' where id = 2199993;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
-- 事务 B
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select username from test_user where id = 2199993;
+----------+
| username |
+----------+
| aaa |
+----------+
1 row in set (0.00 sec)
在这个例子中,事务 A 没有提交,但事务 B 读取到的依然是修改之后的数据,之后如果事务 A 回滚或者再次修改成别的数据,那么 B 那边之前读取到的数据就一直是有问题的。
更加典型的例子是,假如我们修改的是你帐户里余额,本身余额有 100 块,第一笔交易将余额修改成了 80 块,这时第二个交易事务过来,读到了你的余额是 80 块,然后又扣了 10 块钱,正常情况下你的余额应该是 70 块了。但是,注意,第一笔交易的事务出现问题了,无法正常继续交易,于是回滚成 100 块。然而第二笔交易的事务并不知情,依然正常交易并提交了,这时你的余额就变成了 70 元。很明显,这就产生了问题,这个就是脏读带来的结果,一致性出现了问题。
不可重复读
不可重复读是啥意思呢?其实跟上面的是一样概念,只是说我们的 B 事务之后又读了一次 A 事务已经提交的数据,发现两次数据不对呀,这就是不可重复读。官方一点的解释就是:A 事务修改了数据,B 事务在修改前和修改后读取的数据不一样。注意,在不可重复读中,没有回滚的操作,另外,如果两个事务同时都是修改一条数据的话,那么后修改的数据会覆盖前面修改事务的操作结果,这也是不可重复读的问题。
go
-- 事务B
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select username from test_user where id = 2199993;
+----------------------+
| username |
+----------------------+
| 355607b664269a13dae9 |
+----------------------+
1 row in set (0.00 sec)
-- 事务A
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update test_user set username = 'aaa' where id = 2199993;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> commit;
Query OK, 1 row affected (0.00 sec)
-- 事务B
mysql> select username from test_user where id = 2199993;
+----------+
| username |
+----------+
| aaa |
+----------+
1 row in set (0.00 sec)
幻读
最后一个幻读,其实它和前面两个问题的情况也是类似的,都是读取的不一致问题,并且和不可重复读非常类似。当 B 事务读取数据时使用的是聚合方式,比如说查询数量,那么假设 A 事务在 B 事务第一次读取后增加或者删除了数据,那么 B 事务第二次读取的时候这个数量就会发生变化,就好像产生幻觉了一样。
go
-- 事务B第一次读取
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select count(*) from test_user;
+----------+
| count(*) |
+----------+
| 2200001 |
+----------+
1 row in set (0.49 sec)
-- 事务A删除
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> delete from test_user where id < 10;
Query OK, 9 rows affected (0.00 sec)
-- 事务B再次读取
mysql> select count(*) from test_user;
+----------+
| count(*) |
+----------+
| 2199992 |
+----------+
1 row in set (0.37 sec)
咋一看,幻读和不可重复读貌似是一个意思呀?确实,它们非常类似,但是,幻读更强调的是聚合操作结果,而不是单一一条数据的修改,这就是它们两个之间最本质的区别。
总结
好了,问题呈现在眼前了,其实大家应该能看出,事务常见的这三个问题都和数据的一致性读取有关,也就是说,在多个并发事务的前提下,如何保证数据的并发一致性就是我们要面对的问题。那么这些问题是怎么解决的呢?这个就是我们下回要讲到的内容了,也就是 事务隔离 机制相关的知识。