目录
如何理解隔离级别?
隔离级别
查看隔离级别
设置隔离级别
读未提交
读提交
可重复读
串行化
总结
mysql 服务可能同时被多个客户端进程访问,访问的方式以事务进行。
一个事务可能由多条 sql 组成,那么就说明事务就三个状态,执行前、执行中、执行后。而所谓的原子性就是让用户看到要么是执行前,要么是执行后,不会看到执行者的过程,而执行中出现问题可以随时回滚。所以单个事务表现出来就是原子的。
但是事务都是由执行过程的,那么多事务在执行的时候都是有可能会互相影响的,比如多个事务同时访问一张表,甚至是一行记录。
就比如说现在你要去学习,你界定要么就学到最好,要么干脆都别学,而中间的过程并不关心,而你怎么样学习别人并不关心,只要知道你学习到最好就可以了,但是你在学习的过程中容易受到打扰,所以此时你学习的时候就去一个别人不会轻易打扰到你的地方,而这就是隔离性,所以隔离性也是有差别的,如果你去图书馆,那么隔离性就好一点,如果去大街上,那么就几乎没有任何隔离性。
数据库中为了保证事务在执行过程中不受到干扰,就有一个特性——隔离性。
而数据库允许书屋收到不同级别的干扰,也就是隔离级别。
读未提交【Read Uncommitted】: 在该隔离级别,所有的事务都可以看到其他事务没有提交的执行结果。(实际生产中不可能使用这种隔离级别的),但是相当于没有任何隔离性,也会有很多并发问题,如脏读,幻读,不可重复读等。
读提交【Read Committed】 :该隔离级别是大多数数据库的默认的隔离级别(不是 MySQL 默认的)。它满足了隔离的简单定义:一个事务只能看到其他的已经提交的事务所做的改变。这种隔离级别会引起不可重复读,即一个事务执行时,如果多次 select, 可能得到不同的结果。
可重复读【Repeatable Read】: 这是 MySQL 默认的隔离级别,它确保同一个事务,在执行中,多次读取操作数据时,会看到同样的数据行。但是会有幻读问题。
串行化【Serializable】: 这是事务的最高隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决了幻读的问题。它在每个读的数据行上面加上共享锁,。但是可能会导致超时和锁竞争(这种隔离级别太极端,实际生产基本不使用)
查看隔离级别的方法有很多下面一一介绍
查看全局的隔离级别
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-UNCOMMITTED |
+-----------------------+
1 row in set, 1 warning (0.00 sec)
我们前面设置了全局的隔离级别是读未提交,所以现在查询到还是读未提交
查看会话的隔离级别
mysql> select @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| READ-UNCOMMITTED |
+------------------------+
1 row in set, 1 warning (0.00 sec)
一般会话的隔离级别会根据全局的隔离基本来初始化,所以如果设置了全局的隔离级别,如果想让会话的隔离级别也生效需要重启一下客户端mysql。
查看会话的隔离级别(上面是全写,下面是缩写)
mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)
上面是查看隔离级别,我们发现隔离级别既有全局的也有局部的,所以我们设置隔离级别也有这两种
设置全局隔离级别
mysql> set global transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
上面讲全局的隔离级别设置为了读提交,下面查一下隔离级别看一下
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-COMMITTED |
+-----------------------+
1 row in set, 1 warning (0.00 sec)
设置局部隔离级别
mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE |
+----------------+
1 row in set, 1 warning (0.00 sec)
设置局部隔离级别为串行化,下面查询查看
mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE |
+----------------+
1 row in set, 1 warning (0.00 sec)
局部的隔离级别只会影响当前会话,而全局的隔离级别会影响全局的隔离性。
下面开始查看读未提交,读未提交是在两个启动的事务中,只要其中一个事务一旦修改或者跟新了数据,那么在另一个事务中也是可以看到的,这种隔离性几乎没有,而在实际中也几乎没有人会采用这种隔离级别。
首先我们先将隔离级别设置为读未提交
mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)
下面我们开启两个事务,然后在其中一个插入数据,另一个里面查看
mysql> insert into account values(1, '张三', 1111.11);
Query OK, 1 row affected (0.00 sec)
插入后查看
mysql> select * from account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 张三 | 1111.11 |
+----+--------+---------+
1 row in set (0.00 sec)
在其中一个正在运行的事务插入了一条数据,结果还未等该事务结束,被另一正在运行的事务就查看到该事务插入的数据了,这显然是一个问题,按照正常情况下,该事务都未结束,正处于执行中的状态,不应该被其他事务看到执行结果。
而一个事务在执行中,读到另一执行中的事务的修改或者跟新,单未commit 的数据,这种现象就叫脏读。
设置隔离级别为读提交
mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 row in set, 1 warning (0.00 sec)
插入数据
mysql> insert into account values(2, '李四', 2222.22);
Query OK, 1 row affected (0.00 sec)
查看
mysql> select * from account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 张三 | 1111.11 |
+----+--------+---------+
1 row in set (0.00 sec)
这里查看到没有,下面提交一下继续查看
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
查看
mysql> select * from account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 张三 | 1111.11 |
| 2 | 李四 | 2222.22 |
+----+--------+---------+
2 rows in set (0.00 sec)
这里查看到了,负责查看的事务中,查看多次account表,结果发现查询的内容是不一样的,那么这是问题吗?
显然也是一个问题,而该问题就是不可重复读
在同一个事务中,不同的时间段(还在事务的操作中),读取到了不同的值,那么这种就叫做不可重复读。
设置隔离级别为可重复读
mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)
当前设置可重复读成功
插入数据
mysql> insert into account values(3, '王五', 8892.50);
Query OK, 1 row affected (0.00 sec)
查询
mysql> select * from account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 张三 | 1111.11 |
| 2 | 李四 | 2222.22 |
+----+--------+---------+
2 rows in set (0.00 sec)
下面我们提交后继续查询
mysql> select * from account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 张三 | 1111.11 |
| 2 | 李四 | 2222.22 |
+----+--------+---------+
2 rows in set (0.00 sec)
提交后查询和我们前面查询到的结果还是相同,这个就是可重复读。
其实可重复读有幻读问题,但是 mysql 已经解决了。
设置当前隔离级别为串行化
mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE |
+----------------+
1 row in set, 1 warning (0.00 sec)
串行化,就是所有的修改或者跟新的事务都串行运行,也就是一个事务结束后下一个事务才开始,但是对于只进行select来说一般不会串行化,因为select 不会修改数据
插入数据
mysql> insert into account values(4, '赵六', 5732.00);
Query OK, 1 row affected (0.00 sec)
查询
mysql> select * from account;
上面插入数据后,直接查询就阻塞了,下面马上对插入数据的那个事务进行 commit
mysql> select * from account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 张三 | 1111.11 |
| 2 | 李四 | 2222.22 |
| 3 | 王五 | 8892.50 |
| 4 | 赵六 | 5732.00 |
+----+--------+---------+
4 rows in set (32.96 sec)
commit 后查询结果出来了
下面我们只进行 select
mysql> select * from account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 张三 | 1111.11 |
| 2 | 李四 | 2222.22 |
| 3 | 王五 | 8892.50 |
| 4 | 赵六 | 5732.00 |
+----+--------+---------+
4 rows in set (0.00 sec)
mysql> select * from account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 张三 | 1111.11 |
| 2 | 李四 | 2222.22 |
| 3 | 王五 | 8892.50 |
| 4 | 赵六 | 5732.00 |
+----+--------+---------+
4 rows in set (0.00 sec)
只进行 select 的话,并不会阻塞
下面我们可以试一下 写-写 并发
mysql> update account set name='赵6' where id=4;
Query OK, 1 row affected (31.12 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> insert into account values(5, '田七', 888);
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
当在 serializable 的环境下,同时写-写并发就会中断掉一个事务,让另一个事务进行运行。
下面我们总结一下上面的隔离级别以及他们的问题
隔离级别 | 脏读 | 不可重复读 | 幻读 | 加锁读 |
---|---|---|---|---|
读未提交 | 是 | 是 | 是 | 否 |
读提交 | 否 | 是 | 是 | 否 |
可重复读 | 否 | 否 | 是(mysql 没有) | 否 |
串行化 | 否 | 否 | 否 | 是 |
其中上面的问题里面我们有个不可重复读和幻读的问题,下面解释一下这两个问题
不可重复读:主要表示的是在一个运行的事务中删除或者是跟新并且提交后,另一运行的事务读该数据多次读取结果不同
幻读:主要表示的是插在一个运行的事务中插入数据并且提交后,另一事务读该数据发现结果不同
上面就是隔离级别的问题。
其实在实际中,一般读未提交和串行化基本不会用到,因为读未提交基本没有任何隔离性,但是并行高,而串行化就是直接让所有的事务串行的执行,所以效率太慢,在实际中一般还是用到的是读提交和可重复读,而mysql默认是可重复读,并且mysql还解决了可重复读的幻读问题。