MySQL不同隔离级别并发测试分析

背景

事务

事务是数据库执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。事务的出现主要有两个目的,一是提供数据库操作失败时的恢复方法,而是当多个应用程序同时访问数据库时,对其进行隔离,以防止相互干扰。事务具有原子性、一致性、隔离性、持久性四种特性,也就是所谓的ACID特性。在多个应用程序同时访问数据库时,如果不做并发控制,则可能出现脏读、不可重复读、幻读等异常情况。因此,为了各数据库厂商能够更容易地设计并实现数据库,ANSI/ISO SQL 标准规定了四种隔离级别,即READ UNCOMMITTED、READ COMMITTED、REPEATABLE READS以及SERIALIZABLE,明确规定了各个隔离级别下可能出现的异常,见表1。

脏读 不可重复读 幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READS
SERIALIZABLE

基于锁的并发控制

 ANSI/ISO SQL 标准在规定了隔离级别的同时,也提出了基于锁实现并发控制的一种方法,其基本思想是将锁分为三种,即读锁、写锁以及范围锁,读锁和写锁我们都很清楚,那么什么是范围锁?所谓范围锁其实是读锁的一种,但范围锁不是对一条记录加锁,而是对符合条件的一个范围加锁。当使用带WHERE子句的SELECT查询时,为了防止幻读的产生,需要使用范围锁。表2为不同隔离级别下对上述三种锁的持用情况,“是”表示在该隔离级别下需要得到对应的锁,并且直到事务结束才会释放。
下面我们详细分析一下使用锁了进行并发控制的流程,对读操作和写操作分别进行讨论。

写锁 读锁 范围锁
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READS
SERIALIZABLE

写操作:
 在所有的四种隔离级别下,写事务进行操作的流程都是一样的。如果它要对某条记录进行操作,则需要获得该记录的写锁,与此同时,保存该记录的旧值,然后对该记录进行写操作。当然,根据读写锁的相容性矩阵,只有该记录没有被加任何锁的时候才能获得写锁。对于每条记录来说,同一时刻最多只有一个事务能得到写锁,所以最多只会有两个不同的值,一个是旧值,即写入该值的事务已经成功提交,另外一个是新值,写入该值的事务目前处于未提交状态。如果写入该记录的事务成功提交,就删除该记录的旧值,否则则需要恢复该记录的旧值,最后释放该记录的写锁。

读操作:
 在READ UMCOMMITED这个隔离级别下,读事务不需要获得所读记录的读锁,因此进行读事务永远不会阻塞,它会读取该记录最新的值(写入该值的事务可能处于未提交状态)。
 在READ COMMITED这个隔离级别下,读事务需要获得所读记录的读锁,并且与READ UMCOMMITED这个级别不一样,此时读事务会读取该记录最新的已经提交的值。但是需要特别注意的一点是,一旦事务的读操作完成,就会将读锁释放掉,读事务并不会一直持有该读锁。
 在REPEATABLE READS这个隔离级别下,读事务需要获得所需记录的读锁。如果该记录已经被上了写锁,则读事务会阻塞,直到持有该记录写锁的事务提交为止;否则(即该记录未被上锁或者被上了读锁)直接读取该记录(此时该记录只会有一个值),并对该条记录上读锁。读事务会一直持有读锁,直到该事务提交或者回滚时,才会释放读锁。
 在SEREIALIZABLE这个隔离级别下,读事务也需要获得所需记录的读锁。对于一般的读操作,同REPEATABLE READS一样进行处理,即如果该记录已经被上了写锁,则读事务会阻塞,直到持有该记录写锁的事务提交为止;否则直接读取该记录,并对该条记录上读锁。但是对于范围SELECT查询,在这个隔离级别下,需要对符合SELECT条件的记录加范围锁,以保证不会出现幻读。读事务会一直持有读锁或者范围锁,直到该事务提交或者回滚时,才会释放读锁。

MVCC

 MVCC的精要用一句话来概括即是“读不阻塞写,写不阻塞读”。由于MVCC有多种实现方式,此处就不一一列举,MVCC的主要实现机制是保存每个记录的多个版本。由于是对MySQL进行测试,所以详细分析一下在MySQL进行并发控制时MVCC的作用。
 MVCC本质上并不是由MySQL实现的,MySQL将上层与存储分离,提出了一个插件式的架构,底层可以使用不同的存储引擎。在这些存储引擎中,最著名的应该是Innodb存储引擎,其中很重要的一个原因是Innodb是少数几个支持事务的存储引擎之一。
 Innodb使用锁与MVCC相结合的方式实现并发控制,它只在READ COMMITTED以及REPEATABLE READS这两个隔离级别下使用MVCC机制。Innodb把读分为了半一致性读和需要加锁的读,当事务的隔离级别低于SERIALIZABLE时,读都是半一致性读,而当进行半一致性读时,Innodb并不需要对记录行加读锁,Innodb会读取使用MVCC机制保存的旧版本。因此在事务对某条记录进行半一致性读时,其他事务仍然可以进行写操作,与之对应,如果某条记录已经被加了写锁,那么此时其他事务仍然可以进行半一致性读。

测试方法

 本次测试环境要求比较简单,最基本的要求当然是MySQL数据库,并且安装了Innodb存储引擎(一般默认在安装MySQL时已经安装完毕)。测试过程较为简单,下面主要描述测试过程中需要注意的一些关键问题。
 在创建测试表test_pk时后注意一定要使用Innodb作为存储引擎,使用SHOW CREATE TABLE test_pk这个命令可以查看test_pk的存储引擎,因为有时Innodb没有安装成功,会导致创建的表使用默认的MyISAM存储引擎。
CREATE TABLE test_pk (
id int(11) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY id (id)
) ENGINE=InnoDB;
 设置会话的隔离级别使用SET SESSION TRANSACTION ISOLATION LEVEL XXX命令,注意其中SESSION不可省略,这样隔离级别才会对整个会话生效,否则的话设置仅对下一个事务起作用。
测试时必须使用BEGIN或者START TRANSACTION命令开始事务,这样就不会受到Mysql AUTOCOMMIT变量的影响,注意不要使用单行事务(autocommit=1时,Innodb对单行SELECT做了优化,不管在什么隔离级别,都不会加读锁)。

用例与分析

测试结果

表1是测试一(先读后写)的测试结果,表中的项“是”表示写事务阻塞,“否”则表示写事务没有被阻塞。

表1

READ UNCOMMITED READ COMMITTED REPEATABLE READS SERIALIZABLE
READ UNCOMMITED
READ COMMITTED
REPEATABLE READS
SERIALIZABLE

表2是测试二(先写后读)的测试结果,表中的项“是”表示写事务阻塞,“否”则表示写事务没有被阻塞。

表2

READ UNCOMMITED READ COMMITTED REPEATABLE READS SERIALIZABLE
READ UNCOMMITED
READ COMMITTED
REPEATABLE READS
SERIALIZABLE

用例分析

由于Innodb在READ COMMITTED和REPEATABLE READS这两个隔离级别下使用了MVCC机制,下面我们首先分析其他两个隔离级别下的测试结果
 READ UNCOMMITTED/SERIALIZABLE(测试一):会话一的隔离级别为READ UNCOMMITTED,会话二的隔离级别为SERIALIZABLE。会话一在进行SELECT操作的时候不需要获得读锁,并没有对相应的记录加锁,因此当会话二进行UPDATE操作时,可以成功地获得该记录的写锁,并不会阻塞。
 SERIALIZABLE/READ UNCOMMITTED(测试一):会话一的隔离级别为SERIALIZABLE,会话二的隔离级别为READ UNCOMMITTED。会话一在进行SELECT操作的时候需要获得读锁,因此当会话二进行UPDATE操作时,无法获得该记录的写锁,UPDATE操作被阻塞。
 READ UNCOMMITTED/SERIALIZABLE(测试二):会话一的隔离级别为READ UNCOMMITTED,会话二的隔离级别为SERIALIZABLE。会话一在进行UPDATE操作的时候首先获得该记录的写锁,当会话二进行SELECT操作时,由于其隔离级别为SERIALIZABLE,需要获得该记录的读锁,因此SELECT操作被阻塞。
 SERIALIZABLE/READ UNCOMMITTED(测试二):会话一的隔离级别为SERIALIZABLE,会话二的隔离级别为READ UNCOMMITTED。会话一在进行SELECT操作的时候需要获得读锁,因此当会话二进行UPDATE操作时,无法获得该记录的写锁,UPDATE操作被阻塞。
根据上一小节关于纯粹的基于锁的并发控制原理的描述,如果使用纯粹锁的并发控制,测试一的预期结果应 该如表3所示,测试二的预期结果应该如表4所示。
表 3

READ UNCOMMITED READ COMMITTED REPEATABLE READS SERIALIZABLE
READ UNCOMMITED
READ COMMITTED
REPEATABLE READS
SERIALIZABLE

对于测试一,对比表1与表3我们可以看到,预期结果与测试结果在会话一的隔离级别为REPEATABLE READS时不一致,这主要是因为在REPEATABLE READS这个隔离级别下,Innodb使用了MVCC机制,根据关于MVCC的描述,Innodb此时进行的读操作是半一致性读,并不需要对记录加读锁,测试二与此类似,不再赘述。

READ UNCOMMITED READ COMMITTED REPEATABLE READS SERIALIZABLE
READ UNCOMMITED
READ COMMITTED
REPEATABLE READS
SERIALIZABLE

总结

前面我们说过,实现并发控制大致有三种方式,即锁、时间戳以及MVCC。锁应该是最早被用来进行并发控制的机制,时间戳和MVCC是随后出现的,它们各有自己的优点与缺点。在当今的数据库中,很少有单纯使用某一种机制来进行并发控制,大多数都杂合了其中的两种或者三种,各取所长,MySQL的Innodb就是其中的一个。Innodb采用了锁与MVCC结合的方式实现并发控制,在实际运行中取得了不错的效果。

发布了41 篇原创文章 · 获赞 136 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/songguangfan/article/details/94718182
今日推荐