数据库隔离级别与MVCC

Mysql是我们日常生产与学习中最常接触到的数据库之一,今天讲一讲在Mysql(或者说其他类似的数据库)中存在的隔离级别以及用来提高效率的多版本并发控制(MVCC)。

一、隔离级别

首先我们需要提到一个概念:事务。什么是事务?事务就是完成一个基础操作的一系列操作语句的一个集合。例如我要将200元从账户A转移到账户B,那么我可能会进行一下的操作:
a.验证账户A中的余额是否大于200元。
b.将账户A中的余额减200元。
c.将账户B中的余额加200元。
我们就将上面的abc三个操作成为一个事务。
这时,我们会注意到我们所说的一个事务有可能是由多条语句组合而成的,而事务又存在原子性,即事务的执行过程中是不能被打断的,这就带来一个问题,如果在这三步执行过程中有另外的语句插入进来执行,是否会对结果产生影响,因为此时破坏了事务的原子性。而这种插入的情况在并发的环境下是十分常见的。因此,我们(或者说是数据库引擎)就需要在一个事务的执行过程中对它进行“保护”,即保证外界的其他事务的语句不能随意的插进正在执行的事务语句之中,来保证事务的正常执行。这时候我们很容易的会想到“加锁”这个方法。这其实是一种很笼统的说法,因为加锁虽然能够保证事务的正常执行,但是却会带来较大的额外开销,因此合适的时候选择合适的加锁方式对查找效率的影响就非常大。而“锁”得严不严,就区分除了集中不同的隔离级别。

READ UNCOMMITED(读未提交)

这种隔离界别下,读取数据的时候不受任何影响。即你甚至可以读取一个正在被其他事务修改的数据,想读就读,想改就改。这当然开销很小,但是会带来许多的问题,比如“脏读”。即读取到了正在修改但是却还没有提交的数据,这就会造成数据读取的错误。从性能上来说,READ UNCOMMITED不会比其他级别好太多,但是却带来了非常多的麻烦的问题,因此在实际中很少使用这个个立即被。

READ COMMITED(提交读/不可重复读)

这个级别在READ UNCOMMITED的基础上添加了一些规定,是一些数据库的默认隔离级别。它与READ UNCOMMITED的区别在于,它规定读取的时候读到的数据只能是提交后的数据。举个例子,数据a在上一次提交之后的值是1,这时候有一个线程进来对a进行修改,将a修改为2,但是此时并未提交事务(COMMIT),在这种情况下,READ UNCOMMITED级别下读取到的a的值就是当前的2,但是READ COMMITED级别下读取到的还是上一次提交之后的值,即a为1,必须到修改线程将a的值变为2这个事务提交之后读取到的a的值才是2。这个级别所带来的问题就是不可重复读。即上一个时间读取到的a的值是1,但是随着修改线程对事务的提交,a的值变为了2,这时候读到的值就是2了,即执行两次相同的读取操作得到的值却不一样。
不可重复读同脏读的区别在于,脏读是一个事务读取了另一未完成的事务执行过程中的数据,而不可重复读是一个事务执行过程中,另一事务提交并修改了当前事务正在读取的数据。

REPEATED READ(可重复读)

REPEATED READ在READ COMMITED的基础上又添加了一些约束性的规则,它也是MySQL数据库的默认隔离级别。简单来说就是在一个事务的执行期间禁止其他事务对相应的数据进行修改,这就彻底使得一个事务的执行过程中所查询到的数据一定是一致的,即解决了脏读和不可重复读的问题,但是却带来了新的问题,即“幻读”。
“幻读”指的是在一个事务执行过程中虽然禁止了对相应数据的修改,但是其他的事务依然可以插入数据,这时候第一个事务就会发现会“莫名其妙”多出来一些数据,像是出现了幻觉似的。幻读和不可重复读都是读取了另一条已经提交的事务(这点同脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

SERIALIZABLE (可串行化)

这是最严格的一个隔离级别。它通过强制事务串行执行,避免了幻读的问题。但是这种隔离级别的开销极大,一般也不常使用。

各种隔离级别与可能的问题关系如下:

隔离级别 脏读 不可重复读 幻读 加锁
READ UNCOMMITED YES YES YES NO
READ COMMITED NO YES YES NO
REPEATED READ NO NO YES NO
SERIALIZABLE NO NO NO YES

二、MVCC

试想一下,如果每次SQL操作为了保证数据的一致性与准确性,都需要加一个行级锁的话,非常可靠,但是带来的系统开销与查找效率的下降也是非常明显的,因此MVCC就是为了解决这种矛盾而产生的。
首先MVCC会在表中的每一行记录后面保存两个隐藏的列,一个保存行的创建时间,一个保存行的过期(删除)时间。这个时间值并不是真的时间,而是一个系统版本号。事务开始的时刻的系统版本号作为事务的版本号,用来和查询到的每行记录的版本号进行比较。
INSERT:为新插入的每一行保存当前的系统版本号作为行版本号。
DELETE:为删除的每一行保存当前的系统版本号最为行删除版本号。
UPDATE:更新其实应该理解为插入一条新的数据,并删除原来数据的过程,即为新插入的数据保存当前的系统版本号作为行版本号,并为删除的数据保存当前的系统版本号作为删除版本号。
SELECT:只查询满足下列条件的行:
a.行版本号小于等于事务版本号
b.删除版本号未定义或者大于事务版本号

保存了这两个版本号之后绝大多数的操作都可以在不加锁的情况下进行正确的操作,保证了性能和效率。
值得注意的是MVCC只在READ COMMITED和REPEATABLE READ两个隔离级别下工作。

你可能感兴趣的