mysql事务隔离的一点理解

前言

先介绍一下事务的概念
事务(Transaction)就是数据库管理的一个逻辑单位,由一个有效的数据库操作序列构成。

事物ACID特性

  • 原子性(Atomicity):事务作为一个整体被执行,要么全部成功执行,要么全部失败
  • 一致性(Consistency):指的是逻辑上的一致性,即所有操作是符合现实当中的期望的
  • 隔离性(Isolation):多个事务并发时,一个事务不应该影响其他事务的执行
  • 持久性(Durability):被提交过的事务对数据库的修改应该永久保存在数据库中

通俗的理解,就是将一系列的数据库操作(增删改查)看做一个整体,要么所有操作都成功,要么都失败。

一、产生的问题

如果没有事务隔离的话,会发生以下的问题

1. 脏读

脏读是指一个事务读取了另一个事务未提交的数据

2. 不可重复读

不可重复读是指事务读取某行数据,多次读取的结果不同

不可重复读和脏读的区别:不可重复读是事务A读取某行数据后,事务B修改这行数据并提交之后,事务A再去读这行数据,读到了修改后的数据

3. 幻读

幻读是指事务A读某行数据后为100,事务B把这行数据改为99并提交,事务A查看这行数据,发现还是之前修改的数据,但实际这行数据已经是99了,这就是幻读。
上面还有点不清晰,借鉴一下别人的白话解释

幻读,并不是说两次读取获取的结果集不同,幻读侧重的方面是某一次的 select 操作得到的结果所表征的数据状态无法支撑后续的业务操作。更为具体一些:select 某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,此时就发生了幻读。

二、事务隔离

  • Read uncommitted(读未提交)
  • Read Committed(读已提交)
  • Repeatable Reads(可重复读)
  • Serializable(串行化)

1. 读未提交

隔离级别最低的一种事务级别,会发生脏读,不可重复读,幻读

2. 读已提交

读到的都是别人提交后的值。这种隔离级别下,会引发不可重复读和幻读,但避免了脏读。

3. 可重复读

这种隔离级别下,会引发幻读,但避免了脏读、不可重复读。

4. 串行化

最严格的隔离级别。在串行化隔离级别下,所有事务按照次序依次执行。脏读、不可重复读、幻读都不会出现。

三、测试

下面我们在MYSQL下,来对上面四种事务隔离进行测试
在mysql中首先建立一张student表,有如下数据

学生id学生姓名学生性别
1花轮
2小丸子

看一下mysql的默认隔离级别

mysql> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set (0.03 sec)

默认是可重复读,下面我们就来测试

1. 读未提交

首先开启事务A,读表中数据数据

mysql> begin;

mysql> select * from student ;
+----+------------+------+
| id | name       | sex  |
+----+------------+------+
|  1 | 花轮          | 男   |
|  2 | 小丸子        | 女   |
+----+------------+------+
2 rows in set (0.02 sec)

打开另一个窗口,开启事务B,修改表中数据,不提交

mysql> begin ;

mysql> update student set sex='女' where id=1;
Query OK, 0 rows affected (0.00 sec)

再回到事务A中读表数据,看是否改变

mysql> select * from student ;
+----+------------+------+
| id | name       | sex  |
+----+------------+------+
|  1 | 花轮          | 男   |
|  2 | 小丸子        | 女   |
+----+------------+------+
2 rows in set (0.01 sec)

可以发现在mysql下没有发生脏读。把上面两个事务进行回滚(rollback)操作

2. 读已提交

首先开启事务A,读表中数据数据

mysql> begin;

mysql> select * from student ;
+----+------------+------+
| id | name       | sex  |
+----+------------+------+
|  1 | 花轮          | 男   |
|  2 | 小丸子        | 女   |
+----+------------+------+
2 rows in set (0.02 sec)

打开另一个窗口,开启事务B,修改表中数据,并提交

mysql> begin ;

mysql> update student set sex='女' where id=1;
Query OK, 0 rows affected (0.00 sec)

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

再回到事务A中读表数据,看是否改变

mysql> select * from student ;
+----+------------+------+
| id | name       | sex  |
+----+------------+------+
|  1 | 花轮          | 男   |
|  2 | 小丸子        | 女   |
+----+------------+------+
2 rows in set (0.00 sec)

可以发现在mysql下避免了不可重复读。将之前修改的数据改回来

mysql> update student set sex='男' where id=1;

3. 可重复读

开启A事务,读取表中数据

mysql> begin ;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from student ;
+----+------------+------+
| id | name       | sex  |
+----+------------+------+
|  1 | 花轮          | 男   |
|  2 | 小丸子        | 女   |
+----+------------+------+
2 rows in set (0.00 sec)

打开另一个窗口,开启事务B,插入一条数据,并提交

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

mysql> insert into student values ('3','飞飞' ,'女' );
Query OK, 1 row affected (0.01 sec)

mysql> select * from student;
+----+------------+------+
| id | name       | sex  |
+----+------------+------+
|  1 | 花轮          | 男   |
|  2 | 小丸子        | 女   |
|  3 | 飞飞       | 女   |
+----+------------+------+
3 rows in set (0.00 sec)

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

在A事务中查看数据

mysql> select * from student ;
+----+------------+------+
| id | name       | sex  |
+----+------------+------+
|  1 | 花轮          | 男   |
|  2 | 小丸子        | 女   |
+----+------------+------+
2 rows in set (0.00 sec)

可以看出,A事务产生了幻读,因为实际上id=3这行数据是有的。

4. 串行化

前面的测试已经证明,在mysql的REPEATABLE-READ下,事务不会串行。

补充

这里多提一点,就是在REPEATABLE-READ下,事务是会发生死锁的,但mysql会自动给你处理死锁。

表student

学生id学生姓名学生性别
1花轮
2小丸子

表student1

学生id学生姓名学生性别
1qq
2ww

第一步,开启事务A,将student表 id=1 的 sex更改为女

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

mysql> update student set sex='女' where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

第二步,开启事务B,将student1表 id=1 的 sex更改为男

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

mysql> 
mysql> update student1 set sex='男' where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

第三步,在事务A中,将student1表中 id=1的 sex更改为女

mysql> update student1 set sex='女' where id=1;

这里,事务A处于等待状态,因为这个更新操作需要等到事务B处理完成才能进行,而且如果超过一定时间没有处理,会提示超时错误

第四步,在事务B中,将将student1表中 id=1的 sex更改为男

mysql> update student set sex='男' where id=1;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

可以看到在事务B中报错,产生了死锁
再回去看事务A,发现第三步的更新成功了

mysql> update student1 set sex='女' where id=1;
Query OK, 0 rows affected (19.03 sec)
Rows matched: 1  Changed: 0  Warnings: 0

这里涉及到的其实是数据库锁的概念,以后有机会再写篇有关的文章


第一次写文章,有很多不足的地方,请多多见谅~~

相关推荐