MySQL事务的四种隔离级别

239 阅读7分钟

MySQL事务的四种隔离级别

MySQL 中事务的隔离级别一共分为四种,分别如下:

  • 序列化(SERIALIZABLE)
  • 提交读(READ COMMITTED)
  • 可重复读(REPEATABLE READ)
  • 未提交读(READ UNCOMMITTED)

先看一下四种隔离级别的区别

隔离级别脏读不可重复读幻读
READ UNCOMMITTED可能可能可能
READ COMMITTED不可能可能可能
REPEATABLE READ不可能不可能可能
SERIALIZABLE不可能不可能不可能

1.SERIALIZABLE

完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。
如果隔离级别为序列化,则用户之间通过一个接一个顺序地执行当前的事务,这种隔离级别提供了事务之间最大限度的隔离。

2.READ COMMITTED

只能读取到已经提交的数据.READ COMMITTED 隔离级别的安全性比 REPEATABLE READ 隔离级别的安全性要差。处于 READ COMMITTED 级别的事务可以看到其他事务对数据的修改。也就是说,在事务处理期间,如果其他事务修改了相应的表,那么同一个事务的多个 SELECT 语句可能返回不同的结果。

3.REPEATABLE READ

事务不会被看成是一个序列。不过,当前正在执行事务的变化仍然不能被外部看到,也就是说,如果用户在另外一个事务中执行同条 SELECT 语句数次,结果总是相同的。(因为正在执行的事务所产生的数据变化不能被外部看到)。
可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读

4.READ UNCOMMITTED

允许脏读,也就是可能读取到其他会话中未提交事务修改的数据
READ UNCOMMITTED 提供了事务之间最小限度的隔离。除了容易产生虚幻的读操作和不能重复的读操作外,处于这个隔离级的事务可以读到其他事务还没有提交的数据,如果这个事务使用其他事务不提交的变化作为计算的基础,然后那些未提交的变化被它们的父事务撤销,这就导致了大量的数据变化。

SQL案例说明

默认的行为(不带session和global)是为下一个(未开始)事务设置隔离级别。如果你使用GLOBAL关键字,语句在全局对从那点开始创建的所有新连接(除了不存在的连接)设置默认事务级别。你需要SUPER权限来做这个。使用SESSION 关键字为将来在当前连接上执行的事务设置默认事务级别。 任何客户端都能自由改变会话隔离级别(甚至在事务的中间),或者为下一个事务设置隔离级别。

查询隔离级别

全局

SELECT @@global.transaction_isolation, @@transaction_isolation;

当前会话

SELECT @@session.transaction_isolation, @@transaction_isolation;

以上是jdk8版本以后的写法,jdk8之前写法如下:

SELECT @@GLOBAL.tx_isolation, @@tx_isolation;

sql1.png

修改事务隔离级别

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

sql2.png

注意,如果只是修改了当前 session 的隔离级别,则换一个 session 之后,隔离级别又会恢复到默认的隔离级别,所以我们测试时,修改当前 session 的隔离级别即可。

准备数据表格

sql4.png 用例子说明各个级别的情况:

READ UNCOMMITTED

READ UNCOMMITTED 是最低隔离级别,这种隔离级别中存在脏读、不可重复读以及幻象读问题

1.脏读

脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
只有隔离级别为READ UNCOMMITTED会发生脏读

1.打开两个窗口,第一个窗口执行以下指令不提交事务

START TRANSACTION;
UPDATE account set money=money+500 where name='tom';
UPDATE account set money=money-500 where name='jack';
COMMIT;

2.第二个窗口改变会话隔离级别为READ UNCOMMITTED并查询该表

sql6.png

发现脏读到了未提交到数据库的数据。

2.不可重复读

READ UNCOMMITTEDREAD COMMITTED都支持不可重复读
指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
设置窗口一隔离级别为READ COMMITTED执行以下代码不提交事务 sql8.png

窗口二执行并提交事务

START TRANSACTION;
UPDATE account set money=money+500 where name='tom';
COMMIT;

sql9.png

在同一事务中,前后读取结果不尽相同,这就是不可重复读

和脏读的区别在于,脏读是看到了其他事务未提交的数据,而不可重复读是看到了其他事务已经提交的数据(由于当前 SQL 也是在事务中,因此有可能并不想看到其他事务已经提交的数据)

3.可重复读

REPEATABLE READSERIALIZABLE支持可重复读 还是上述代码,若将隔离级别改为REPEATABLE READ,发现两次读取的数据相同,其他事务的执行和提交并不影响该事务内数据

4.幻读

READ UNCOMMITTEDREAD COMMITTEDREPEATABLE READ都可能幻读
第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。 1.窗口一执行前两行代码开启事务读取表中数据,此时表中只有tom和jack两条数据

START TRANSACTION;
SELECT * from account;
delete from account where name='jess';
COMMIT;
  1. 此时窗口二也执行前两行数据开启事务向表格中插入一条数据,不用提交事务。
START TRANSACTION;
insert into account(name,money) values('jess',1000);
COMMIT;

3.执行窗口一第三行,由于脏读问题,此时可以查询到 jess 这个用户。 4. 执行窗口二的第三行,去删除 name 为 jess 的记录,这个时候删除就会出问题,虽然在窗口一中可以查询到 jess,但是这条记录还没有提交,是因为脏读的原因才看到了,所以是没法删除的。此时就产生了幻觉,明明有个 jess,却无法删除。

5.隔离级别最高SERIALIZABLE

在这种隔离级别中,事务一个接一个顺序的执行,不会发生脏读、不可重复读以及幻象读问题,最安全。

如果设置当前事务隔离级别为 SERIALIZABLE,那么此时开启其他事务时,就会阻塞,必须等当前事务提交了,其他事务才能开启成功,因此脏读、不可重复读以及幻象读问题这里都不会发生。

代码演示

分别开启两个窗口,一个将钱数修改为200,一个修改为500,执行如下代码

START TRANSACTION;
UPDATE account SET money = 200 WHERE id = 1
START TRANSACTION;
UPDATE account SET money = 500 WHERE id = 1
COMMIT;

由于隔离级别为序列化,第一个事务没提交,第二个SQL事务开启后并不能提交,而是会一直等待第一个事务提交

sql5.png