对数据库事物的理解

什么是事物

事物通俗的来讲就是就是一组操作事件,可以类比于Java里面的原子操作。在一个事物中,要么全部成功,要么就是全部失败。

mysql中的事物

在Mysql的innodb中,事物的默认级别是 可重复读,在该级别下,事物可能出现幻读。出现幻读的情况是该引擎为行级锁,导致mysql在进行一个事物的时候只会锁定与该事物有关的几行。

示例如下:

新建一个表,其中的数据如下:

1
2
3
4
5
6
7
8
9
mysql> select * from curd;
+----+------+
| id | name |
+----+------+
| 3 | 4 |
| 4 | 3 |
| 5 | 2 |
| 6 | 1 |
+----+------+

开启一个事物:

1
2
3
4
5
6
START TRANSACTION;
SELECT * FROM curd;
UPDATE curd SET curd.name='1000';
SELECT * FROM curd;
-- SELECT * FROM curd;
-- COMMIT;

此时两次查询的结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM curd;
+----+------+
| id | name |
+----+------+
| 3 | 4 |
| 4 | 3 |
| 5 | 2 |
| 6 | 1 |
+----+------+
4 rows in set (0.00 sec)

mysql> UPDATE curd SET curd.name='1000';
Query OK, 4 rows affected (0.00 sec)
Rows matched: 4 Changed: 4 Warnings: 0

mysql> SELECT * FROM curd;
+----+------+
| id | name |
+----+------+
| 3 | 1000 |
| 4 | 1000 |
| 5 | 1000 |
| 6 | 1000 |
+----+------+
4 rows in set (0.00 sec)

此时可以看到,在事物一未提交之前,事物一做了一个全表的更新,将该表的数值全部更新为1000了。
然后开启 事物二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM curd;
+----+------+
| id | name |
+----+------+
| 3 | 4 |
| 4 | 3 |
| 5 | 2 |
| 6 | 1 |
+----+------+
4 rows in set (0.00 sec)

mysql> INSERT INTO curd(curd.id,curd.name) VALUES(7,'7');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
-- 注意使用win10的管理员cmd会卡在这一行,但是使用sqlyog就不会,mysql会卡在这一行

当事物二执行完这一句之后,回到事物一,然后查询表数据。

1
2
3
4
5
6
7
8
9
10
11
12
-- 使用Sqlyog新增之后看到的数据
mysql> SELECT * FROM curd;
+----+------+
| id | name |
+----+------+
| 3 | 1000 |
| 4 | 1000 |
| 5 | 1000 |
| 6 | 1000 |
| 7 | 7 |
+----+------+
4 rows in set (0.00 sec)

此时就是幻读,事物一明明就更新了6条数据,为什么会出现7条数据。。
然后事物一提交:

1
2
mysql> commit;
Query OK, 0 rows affected (0.01 sec)

此时数据库中的数据已经全部更新成1000,但是在事物二中:

1
2
3
4
5
6
7
8
9
10
11
mysql> SELECT * FROM curd;
+----+------+
| id | name |
+----+------+
| 3 | 4 |
| 4 | 3 |
| 5 | 2 |
| 6 | 1 |
| 7 | 7 |
+----+------+
5 rows in set (0.00 sec)

说明两个事物之间不能读取其他事物未提交的更改,并且在mysql的该级别下,事物开始和结束的时候所读取的数据是一样的。

此时提交事物二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM curd;
+----+------+
| id | name |
+----+------+
| 3 | 1000 |
| 4 | 1000 |
| 5 | 1000 |
| 6 | 1000 |
| 7 | 7 |
+----+------+
5 rows in set (0.00 sec)

可以看到此时数据库中的数据已经如成为了上面所示了。

原理

具体是因为mysql的innodb使用的是行级锁,而且在mysql的每一个表里面都会有两个隐藏的列,分别是创建的时候版本号和删除的时候的版本号。
所以每一个事物执行的时候的版本号都会被该事物号所更新,因此会导致有的事物在执行期间会导致看到不同的数据。

注意

现在mysql5.7貌似已经解决了幻读。。用命令行进行测试的。

作者

Somersames

发布于

2018-08-10

更新于

2021-12-05

许可协议

评论