数据库隔离级别及实现原理

在平常的开发过程中,我们一般都会跟数据库打交道, 今天就来介绍下数据库中的隔离级别以及对应的实现原理.

Read Uncommited(未提交读)

在一个事务中,可以读取到其他事务未提交的数据变化, 这种读取其他会话还没有提交的事务叫做脏读现象, 在生成环境中几乎没有任何使用. 本篇博客也不会做特别详细的介绍.

Read Commited(已提交读)

在Read Commited隔离级别, 不会出现脏读,也不会出现脏写.

No dirty reads

如果一个事务正在往数据库中写入数据, 但是此事务还没有提交或者终止, 如果另外一个事务能够看到未提交事务待提交的数据,这就是脏读. 下图展示的是非脏读

mysql的rc隔离级别会存在脏读 mysql的隔离级别实现原理_隔离级别

从上图中可以看出, 用户2只能看到用户1提交事务之后的数据, 在用户1运行命令set x=3时, 用户2查询x的值,看到的还是2.

No dirty writes

如果一个事务在写数据到数据库,并且事务还没有提交, 如果另外一个事务覆盖了前一个事务未提交的数据,就叫做dirty write(脏写).如下图所示:

mysql的rc隔离级别会存在脏读 mysql的隔离级别实现原理_mysql的rc隔离级别会存在脏读_02

从上图可以看出Alice执行第一个update语句后,Bob执行也执行了update语句.可以看到Bob更新操作覆盖了Alice的更新操作;后面Alice的第二个update覆盖了Bob的第二个update语句.最后造成Listings表和Invoices表中的数据不一致.

实现read commited原理

如何避免dirty writes: 数据库使用行级锁来避免dirty writes.如果一个事务想要修改一条记录,它一定要首先获取这条记录上的锁.直到事务提交或者终止,持有的锁才被释放.如果此记录的锁正在被一个事务持有,那么其他想要修改同一条记录的事务只能等待.

如何避免dirty reads: 我们也可以采用类似避免dirty writes的方法来避免dirty reads. 在读取某一个记录前,首先获取此记录上的锁,在读取完成后,在释放锁.但是这种方法效率很低,如果有一个需要长时间运行的写事务在运行,那么在锁被持有的期间,读取此记录的事务则需要等待.

由于效率的问题,大多数数据库都采用另外一种方法来避免dirty reads. 对于每个被写的对象,数据库记录之前的旧值以及被当前事务持有写锁的新值.当事务执行时,读取正在被修改的记录时,数据库返回旧值.当之前的事务提交后,才返回新值.

Repeatable Read(可重复读)

MySQL默认隔离级别.在一个事务中,直到事务结束前,都可以反复读取到事务刚开始时看到的数据,在此期间,数据不会发生变化,避免了脏读,不可重复读现象,但是它无法解决幻读问题.

不可重复读

一个事务读某条数据读两遍,读到不一样的数据,也就是说,一个事务在进行读取操作中读取到了其他事务对旧数据的修改. 比如开了一个事务, 修改某条数据, 先查询后修改, 在执行修改操作的时候发现这条数据已经被别的事务删除了.或者如下图所示展示的例子:

mysql的rc隔离级别会存在脏读 mysql的隔离级别实现原理_数据库_03

在上图中,可以将Account1和Account2理解为account表中的两条记录.用户Alice在银行有两个账户,她现在把账户1中的100元转到账户2中.在Alice的事务操作中,首先查询账户1的余额是500,在查询账户2的余额之前,有另一个事务将账户1中的100元转移到账户2中,此时Alice在查询账户2中的余额发现是400元;而两个账户的总金额并不是1000元而是900元.此时用户Alice肯定会搞到不可思议.而在这个例子中,数据库是符合read commited隔离级别的.因为Alice执行第二次查询时,看到的是另外一个事务执行完成后的数据状态.

Repeatable Read实现原理