MySQL之隔离级别

MySQL 刘宇帅 4年前 阅读量: 884

事务隔离性(isolation)

数据库中多个事务并发执行时是相互独立的,一个事务不会影响另外一个事务的执行,事务与事务的隔离是有不同级别的,不同级别情况下事务之间的影响不一样。
不同的事务隔离性会给数据库带来问题

不隔离的事务问题

脏读

脏读是指一个事务执行过程中读取到其他事务更新的数据,但是更新数据的事务最后有因为各种原因回滚了,这种想象叫做脏读,因为第一个事务查到的是一个无意义的数据。脏读举例如下

// 事务1
start transaction;
update students set sex=2 where name='洛基';
rollback;

// 事务2
select * from students where name='洛基';

如上事务1我们把洛基变成男性,而当事务1执行第二条语句后,事务2正好执行第一条,那么事务2查到的洛基性别如果2,这种现象叫做脏读。

不可重复读

不可重复读是指一个事务内的多次查询同一条数据却返回了不同的结果,这是因为在查询过程中数据被另外一个事务修改了。还以修改 洛基 性别为例子

//事务1
start transaction;
select * from students where name='洛基';
select * from students where name='洛基';
commit;

// 事务2
start transaction;
update students set sex=2 where name='洛基';
commit;

当事务1执行第1条 select 语句返回的洛基性别为 女,而在事务1执行第二条 select 语句前 事务2 正好开始执行别提交了,那么事务1的第二个 select 语句查询返回的洛基性别为 男。这种再同一个事务中统一个语句查询相同的数据却返回了不同的值得想象叫做不可重复读。

幻读

幻读是指一个事务内多次查询同一条件下的数据返回的行数不一致,这是因为在同一事务多次查询中有其他事务插入了新的数据导致的。我们以新同学入职为例

// 事务1
start transaction;
select * from students;
select * from students;
commit;

// 事务2
start transaction;
insert into students (`no`,`name`,`sex`,`age`) values ('0006','铁蛋',2,25);
commit;

当事务1执行第一条 select 语句时返回了4条语句,而在事务1执行第二条 select 语句前 事务2正好插入了一条新的数据并提交,那么当事务1第二个 select 执行时返回的数据为 5 条,就如同幻觉一般突然多了一条,这种现象叫做幻读。
不可重复读和幻读的都是两次读取到的数据不一致,但是不可重复读针对的是单条数据而幻读是指多条数据的新增和更新,不可重复读可通过锁行解决而幻读一般需要表锁解决(MySQL通过MVCC解决)。

数据库定义了四种事务隔离级别

READ UNCOMMITTED 未提交读、读未提交

未提交读就是事务再未提交的时候的修改的数据是可以别其他事务读取的,这就是上面的脏读的问题。这种隔离级别是最低的隔离级别。几乎所有数据库不会使用该级别的隔离。

READ COMMITTED 提交读、读已提交

读已提交级别满足数据库事务的基本隔离特性:一个事务的数据再未提交之前是不可见的,只有事务提交后数据才能被其他事务读取。读已提交可以解决读未提交的脏读问题,读已提交又叫做不可重复读,即读已提交存在上面提到的不可重复读的问题。大部分数据库的默认级别是 读已提交,但是MySQL不是。

REPEATABLE READ 可重复读

可重复读保证在一个事务中的多次查询相同条件下的数据返回的结果是一致的,也就是解决了读已提交的不可重复读和脏读的问题。但是可重复读无法解决幻读的问题。可重复读是 MySQL 的默认隔离级别,MySQL 的 InnoDB 和 XtraDB 存储引擎使用多版本并发控制(MVCC multiversion concurrency control)解决幻读问题。

SERIALIZABLE 可串行化

可串行化是最高的隔离级别,它通过强制事务串行执行避免前面的幻读问题。可串行化会在读取的每一行数据上都枷锁,所以会非常影响系统的性能,一般情况下没有业务会选择可串行化的隔离级别。

MySQL 默认事务级别

MySQL 默认使用可重复读事务级别并解决了幻读的问题,我们一下验证脏读、不可重复读、幻读的问题。
首先我们开启事务1并查询洛基性别

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

mysql> select * from students where id=1;
+----+------+--------+-----+------+---------------------+---------------------+
| id | no   | name   | sex | age  | created_at          | updated_at          |
+----+------+--------+-----+------+---------------------+---------------------+
|  1 | 0001 | 洛基   |   1 |   25 | 2019-12-20 20:00:00 | 2019-12-20 01:16:41 |
+----+------+--------+-----+------+---------------------+---------------------+
1 row in set (0.00 sec)

然后我们在新的连接里更新洛基性别为男

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

我们再在事务1里查询洛基性别,如下依然为女,这里就可以看出MySQL解决了不可重复读的问题

mysql> select * from students where id=1;
+----+------+--------+-----+------+---------------------+---------------------+
| id | no   | name   | sex | age  | created_at          | updated_at          |
+----+------+--------+-----+------+---------------------+---------------------+
|  1 | 0001 | 洛基   |   1 |   25 | 2019-12-20 20:00:00 | 2019-12-20 01:16:41 |
+----+------+--------+-----+------+---------------------+---------------------+
1 row in

而后我们在事务1里更新洛基性别为男

mysql> update students set sex=2 where id=1;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1  Changed: 0  Warnings: 0

我们再在新的连接里查询洛基性别,如下依然为女,这就可以看出MySQL解决了脏读的问题。

mysql> select * from students where id=1;
+----+------+--------+-----+------+---------------------+---------------------+
| id | no   | name   | sex | age  | created_at          | updated_at          |
+----+------+--------+-----+------+---------------------+---------------------+
|  1 | 0001 | 洛基   |   1 |   25 | 2019-12-20 20:00:00 | 2019-12-20 01:16:41 |
+----+------+--------+-----+------+---------------------+---------------------+
1 row in set (0.00 sec)

我们再在事务1里查询所有的学生,如下

mysql> select * from students;
+----+------+--------+-----+------+---------------------+---------------------+
| id | no   | name   | sex | age  | created_at          | updated_at          |
+----+------+--------+-----+------+---------------------+---------------------+
|  1 | 0001 | 洛基   |   1 |   25 | 2019-12-20 20:00:00 | 2019-12-20 01:16:41 |
|  2 | 0002 | 冷锋   |   2 |   25 | 2019-12-20 20:00:00 | 2019-12-20 20:00:00 |
|  3 | 0003 | 六一   |   2 |   25 | 2019-12-20 20:00:00 | 2019-12-20 20:00:00 |
|  4 | 0004 | 杨过   |   2 |   25 | 2019-12-20 00:26:14 | NULL                |
|  6 | 0006 | 铁蛋   |   2 |   25 | 2019-12-20 01:51:46 | NULL                |
+----+------+--------+-----+------+---------------------+---------------------+
5 rows in set (0.00 sec)

而后我们再新的连接里入学一个新同学,阿末

mysql> insert into students (`no`,`name`,`sex`,`age`) values ('0007','阿末',1,25);
Query OK, 1 row affected (0.00 sec)

我们再在事务1里查询所有学生,如下依然不包含阿末,这就可以看出MySQL解决了幻读的问题。

mysql> select * from students;
+----+------+--------+-----+------+---------------------+---------------------+
| id | no   | name   | sex | age  | created_at          | updated_at          |
+----+------+--------+-----+------+---------------------+---------------------+
|  1 | 0001 | 洛基   |   1 |   25 | 2019-12-20 20:00:00 | 2019-12-20 01:16:41 |
|  2 | 0002 | 冷锋   |   2 |   25 | 2019-12-20 20:00:00 | 2019-12-20 20:00:00 |
|  3 | 0003 | 六一   |   2 |   25 | 2019-12-20 20:00:00 | 2019-12-20 20:00:00 |
|  4 | 0004 | 杨过   |   2 |   25 | 2019-12-20 00:26:14 | NULL                |
|  6 | 0006 | 铁蛋   |   2 |   25 | 2019-12-20 01:51:46 | NULL                |
+----+------+--------+-----+------+---------------------+---------------------+
5 rows in set (0.00 sec)

注: MySQL 默认会对update的数据加锁,所以在事务中更新的数据行会别锁直到事务提交或回滚。

MySQL对其他隔离级别的支持

MySQL 支持 SQL 标准定义的四种隔离级别,默认使用可重复读隔离级别。使用如下语句切换隔离级别

set session transaction isolation level read committed;
set session transaction isolation level read committed;
set session transaction isolation level repeatable read;
set session transaction isolation level repeatable read;

提示

功能待开通!


暂无评论~