Read Uncommited

在RU模式下,即使事务没有commit,在其他事务中仍可以读到未提交的数据。
RU是所有隔离级别中最低的一种。RU模式会导致脏读

Read Commited

RC模式下,事务只能读取到已经commit的数据。
比如事务a在执行时,如果事务b没有提交,a是读不到b的数据的。如果b提交a便能读到b修改的数据。
RC可以避免脏读,但是会导致不可重复读。
大部分系统使用的是RC模式

Repeatable

RR模式下,事务的多次Read不会受其他事务的影响(无论提交与否)
可以理解为RR模式下事务a在创建的时候获取了一次当前时刻数据的快照,快照不受其余事务的影响。
RR模式可能会导致幻读,因为无法感知其余事务,可能导致重复的插入。
mysql innoDB的RR模式可以一定程度避免幻读,该特性是通过间隙锁(gap lock)来实现的,间隙锁的原理可以参照这篇文章http://www.jianshu.com/p/bf86...

Serializable

串行化,顾名思义,是将所有读写操作完全串行。
串行化是所有隔离级别中最高的
每次读都需要获得表级共享锁,读写相互都会阻塞
串行化对资源的开销大,对并发支持不好,只在,某些场景下使用。
最后给出四个隔离级别对三种读类型的对照表:

隔离级别 脏读 不可重复读 幻读
Read Uncommited 可能 可能 可能
Read Commited 不可能 可能 可能
Repeatable Read 不可能 不可能 可能
Serializable 不可能 不可能 不可能

实验

read uncommitted

1.创建表

MySQL [test]> create table `transaction_test`(`id` int(11) not null auto_increment,`num` int(11) not null default '0', primary key (`id`))engine=InnoDB;
Query OK, 0 rows affected (0.02 sec)

2.对A事务,设置隔离级别为未提交读

MySQL [test]> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
MySQL [test]> select @@tx_isolation;
+------------------+
| @@tx_isolation   |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set (0.00 sec)

3.开启A事务,查表

MySQL [test]> start transaction;
Query OK, 0 rows affected (0.00 sec)

MySQL [test]> select * from transaction_test;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |   2 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)

3.开启B事务

MySQL [test]> start transaction;
Query OK, 0 rows affected (0.00 sec)

4.事务B更新id=1的num

MySQL [test]> update transaction_test set num='10' where id='1';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

MySQL [test]> select * from transaction_test;
+----+-----+
| id | num |
+----+-----+
|  1 |  10 |
|  2 |   2 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)

5.B事务未提交,A事务再次查询,发现id=1的行被修改

MySQL [test]> select * from transaction_test;
+----+-----+
| id | num |
+----+-----+
|  1 |  10 |
|  2 |   2 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)

这就是所谓的脏读;

经过上面的实验可以得出结论,事务B更新了一条记录,但是没有提交,此时事务A可以查询出未提交记录。造成脏读现象。未提交读是最低的隔离级别。

read committed

1.设置A的隔离级别为RC,开启事务

MySQL [test]> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)

MySQL [test]> start transaction;
Query OK, 0 rows affected (0.00 sec)

MySQL [test]> select * from transaction_test;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |   2 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)

2.B开启事务,更新id=1的行,但不提交事务

MySQL [test]> start transaction;
Query OK, 0 rows affected (0.00 sec)

MySQL [test]> update transaction_test set num='10' where id='1';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

MySQL [test]> select * from transaction_test;
+----+-----+
| id | num |
+----+-----+
|  1 |  10 |
|  2 |   2 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)

3.因为B事务未提交,所以A再次select,不会查询到变化的数据

MySQL [test]> select * from transaction_test;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |   2 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)

4.B提交事务

MySQL [test]> commit;
Query OK, 0 rows affected (0.01 sec)

MySQL [test]> select * from transaction_test;
+----+-----+
| id | num |
+----+-----+
|  1 |  10 |
|  2 |   2 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)

5.A再次select,发现数据已发生变化,说明B提交的修改被事务中的A读到了,这就是所谓的不可重复读

MySQL [test]> select * from transaction_test;
+----+-----+
| id | num |
+----+-----+
|  1 |  10 |
|  2 |   2 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)

repeatable read

1.将A的隔离级别设为repeatable read

MySQL [test]> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)

2.B开启事务,更新id=1的行并提交事务

MySQL [test]> start transaction;
Query OK, 0 rows affected (0.00 sec)

MySQL [test]> select * from transaction_test;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |   2 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)

MySQL [test]> update transaction_test set num='10' where id='1';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

MySQL [test]> commit;
Query OK, 0 rows affected (0.00 sec)

MySQL [test]> select * from transaction_test;
+----+-----+
| id | num |
+----+-----+
|  1 |  10 |
|  2 |   2 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)

3.A再次查询

MySQL [test]> select * from transaction_test;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |   2 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)

发现数据依然未发生变化,这说明这次可以重复读了

4.重新将id=1的num改为1,并开启A事务;

MySQL [test]> update transaction_test set num='1' where id='1';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

MySQL [test]> select * from transaction_test;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |   2 |
|  3 |   3 |
+----+-----+
3 rows in set (0.01 sec)

MySQL [test]> start transaction;
Query OK, 0 rows affected (0.00 sec)

MySQL [test]> select * from transaction_test;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |   2 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)

2.开启B事务,插入一行,id=4,不提交

MySQL [test]> start transaction;
Query OK, 0 rows affected (0.00 sec)

MySQL [test]> insert into transaction_test values('4', '4');
Query OK, 1 row affected (0.00 sec)

MySQL [test]> select * from transaction_test;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |   2 |
|  3 |   3 |
|  4 |   4 |
+----+-----+
4 rows in set (0.00 sec)

3.A查询,查不到id=4的记录

MySQL [test]> select * from transaction_test;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |   2 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)

4.A插入相同的记录

MySQL [test]> insert into transaction_test values('4', '4');


ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

会一直阻塞,无法插入;

5.B事务提交刚刚的插入

MySQL [test]> commit;
Query OK, 0 rows affected (0.00 sec)

6.A事务再次插入

MySQL [test]> insert into transaction_test values('4', '4');
ERROR 1062 (23000): Duplicate entry '4' for key 'PRIMARY'

7.A事务查询

MySQL [test]> select * from transaction_test;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |   2 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)

报了主键冲突,这里事务A就是发生了幻读,因为事务A读取的数据状态并不能支持它的下一步的业务

serializable

1.删掉上一步插入的id=4的行,设置隔离级别为serializable

MySQL [test]> delete from transaction_test where id=4;
Query OK, 1 row affected (0.01 sec)

MySQL [test]> select * from transaction_test;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |   2 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)

MySQL [test]> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE   |
+----------------+
1 row in set (0.00 sec)

2.开启事务A,事务B

MySQL [test]> start transaction;
Query OK, 0 rows affected (0.00 sec)

MySQL [test]> select * from transaction_test;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |   2 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)
MySQL [test]> start transaction;
Query OK, 0 rows affected (0.00 sec)

MySQL [test]> select * from transaction_test;
+----+-----+
| id | num |
+----+-----+
|  1 |   1 |
|  2 |   2 |
|  3 |   3 |
+----+-----+
3 rows in set (0.00 sec)

3.事务B插入id=4的行

MySQL [test]> insert into transaction_test values('4', '4');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

若事务A不提交,会一直阻塞,直至超时

4.事务A提交

MySQL [test]> commit;
Query OK, 0 rows affected (0.00 sec)

5.事务B插入成功

MySQL [test]> insert into transaction_test values('4', '4');
Query OK, 1 row affected (4.78 sec)

SERIALIZABLE是最高的隔离级别。它通过强制事务串行执行,避免了前面
说的幻读的问题。
简单来说,SERIALIZABLE会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用的问题。实际应用中也很少用到这个隔离级别,只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑采用该级别。

参考文章

https://segmentfault.com/a/11...
高性能mysql 1.3.1章


byte
106 声望13 粉丝