mysql隔离级别

mysql隔离级别

Posted by BY on November 21, 2016

mysql隔离级别

ACID是一组数据库事务属性,用来保证即使在有错误,或断电等情况下数据库数据的正确性。

具体来说,隔离的定义是: 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的。

在数据库系统中,人们定义了4个隔离级别:读未提交,读已提交,可重复读和可串行化。有一些读取现象,例如脏读取,不可重复读取,幻像读取和写倾斜。

  • Read Uncommitted(读未提交) 在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
  • Read Committed(读已提交) 一个事务只能看见已经提交事务所做的改变。这种隔离级别也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
  • Repeatable Read(可重读) 这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
  • Serializable(可串行化) 这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。 这四种隔离级别采取不同的锁类型来实现。并发控制中读取同一个表的数据,可能出现如下问题:

脏读(Drity Read):事务T1修改了一行数据,事务T2在事务T1提交之前读到了该行数据。

不可重复读(Non-repeatable read): 事务T1读取了一行数据。 事务T2接着修改或者删除了改行数据,当T1再次读取同一行数据的时候,读到的数据时修改之后的或者发现已经被删除。

幻读(Phantom Read): 事务T1读取了满足某条件的一个数据集,事务T2插入了一行或者多行数据满足了T1的选择条件,导致事务T1再次使用同样的选择条件读取的时候,得到了比第一次读取更多的数据集。

这是隔离级别和读现象之间的矩阵:

iso-trx.png

我将运行一些测试来验证这些结论,方法是:我尝试起动2个并发事务。具体来说,我打开了2个终端选项卡,每个选项卡都作为一个数据库连接打开。在第一个连接上,我将首先用begin关键字开启一个事务,暂时不调用rollback或commit,这样就可以支持长时间的查询了。同时,在第2个终端,我将运行一些查询以进行测试。

此测试在mysql5.7上运行。

  1. 测试未提交隔离级别

该测试将显示,尽管第个事务尚未完成,但它可以在第二个事务中看到一些“脏数据”。

第一个终端:在开启事务之前,我们将隔离级别临时设置为READ UNCOMMITTED。然后我们查询以获得样本数据。

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;
SELECT year FROM foo WHERE id = 1; -- return 1993

第二个终端:我们创建一个新事务,然后更新同一记录不提交事务。

BEGIN;
UPDATE foo SET year = 2019 WHERE id = 1;

第一个终端:在同一个事务中,我们尝试再次获得相同的记录。但是此时,我们从获得了第二个事务更新的新数据。

SELECT year FROM foo WHERE id = 1; -- return 2019
COMMIT;

在现实中,这种行为很危险,因为第二个事务可能失败并回滚,或者我们只能看到一个事务的中间状态。此行为很容易导致数据不一致。

如果我们再次尝试上述所有步骤,但将隔离级别从未提交更改为读已提交,则在最后一个查询中返回的数据仍为1993。

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

2.测试READ COMMITTED隔离级别

该测试将表明,在读已提交隔离级别中,我们有一个问题:在同一事务中,一行被检索两次,并且两次读取之间的值不同。

在第一个终端上:作为测试1,我们设置隔离级别并查询一些数据。

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
SELECT COUNT(*) FROM foo WHERE year <= 1995; -- total 3 rows
*************************** 1. row ***************************
count(*): 3
1 row in set (0.000 sec)

在第二个终端上:我们尝试将一条记录更新为新值。此更新将影响上一个事务的查询结果。

BEGIN;
UPDATE foo SET year = 2019 WHERE id = 1;
COMMIT;

同样,在第一个终端上,在没有关闭事务的情况下,我们再次运行相同的查询,并发现在同一事务中,相同的查询数据可能会有所不同。

SELECT COUNT(*) FROM foo WHERE year <= 1995; -- return 2
COMMIT;
*************************** 1. row ***************************
count(*): 3
1 row in set (0.000 sec)

如果我们将隔离性从读已提交提高到可重复读,则将解决此问题。

3.测试REPEATABLE READ隔离级别

在此测试中,我想证明,如果我们使用REPEATABLE READ隔离级别,我们将遇到“幻读”问题:在同一事务上,当我们在其他事务上插入新记录时,同一查询有2个不同的结果。

在第一个终端上:

SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT COUNT(*) FROM foo WHERE year <= 1995; -- return 3

在第二个终端上,我尝试插入一条记录:

BEGIN;
INSERT INTO `foo` (name, year) VALUES (foo_foo, 1995);
COMMIT;

在第一个终端上,我尝试再次运行相同的查询,并期望结果将从3变为4,因为可重复读无法检测到幻读:

SELECT COUNT(*) FROM foo WHERE year <= 1995; -- return 3
*************************** 1. row ***************************
count(*): 3
1 row in set (0.000 sec)

这不是我们所期望的。因此,我又尝试了一步,尝试更新插入的记录,希望它会失败:

UPDATE foo SET year = 2019 WHERE name = foo_foo;
COMMIT;
Query OK, 1 row affected (0.000 sec)
Rows matched: 1  Changed: 1  Warnings: 0

看起来执行成功了,我再次通过查询进行测试以进行确认:

SELECT * FROM foo WHERE name = foo_foo;
*************************** 1. row ***************************
id: 7
name: foo_foo
year: 2019
1 row in set (0.000 sec)

因此,基于这2个测试,我对MySQL的REPEATABLE READ隔离级别有一些总结:

  • 当使用select语句时,不会像SQL标准提到的那样幻读。
  • 当事务修改数据(写入/删除/更新)时,我们可以成功地写入“看不见的数据”。该行为是可重复读(未修改的行不可见)和已提交读(已修改的行可见)的混合行为。

4.测试SERIALIZABLE隔离级别

最后,我尝试检查最后的隔离级别:Serializable隔离级别。

第一个终端:

SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
SELECT COUNT(*) FROM foo WHERE year <= 1995; -- return 3 as usual

在第二个终端上:我尝试插入新记录。

BEGIN;
INSERT INTO `foo` (name, year) VALUES (foo_foo, 1996);  waiting
COMMIT;

我们看到第二个事务被阻塞了。或者,换句话说,第个事务被锁定并等待第一个事务完成。

因此,在第一个终端上,我们COMMIT;之后,第二个终端上的事务将自动完成。

参考:

http://mysql.taobao.org/monthly/2017/06/07/

https://medium.com/@huynhquangthao/mysql-testing-isolation-levels-650a0d0fae75