不想睡觉的橘子君

不想睡觉的橘子君

说起mysql,即使是初学者也知道,数据库在执行事务的时候,会遇到几种问题,如脏读、不可重复读,幻读,为了应对这几种问题,有了如下几种事务隔离级别,分别解决了什么什么问题等[1]。本文要讲的就是隔离级别的锁机制,和他们解决了什么问题,以及它们用了什么手段去解决问题。
【面试题】什么是事务,什么是脏读、不可重复读、幻读,以及MySQL的几种事务隔离级别的应对方法-LMLPHP

1,什么是事务

在讲这些问题的时候,有一个首要的问题需要明确,什么是事务。

事务的定义如下:

数据库中的事务是指对数据库执行一批操作,在同一个事务当中,这些操作最终要么全部执行成功,要么全部失败,不会存在部分成功的情况。

事务是一个原子操作。是一个最小执行单元。可以甶一个或多个SQL语句组成

在同一个事务当中,所有的SQL语句都成功执行时,整 个事务成功,有一个SQL语句执行失败,整个事务都执行失败。[2]

事务需要满足4个特性(ACID),

原子性(Atomicity)
事务的整个过程如原子操作一样,最终要么全部成功,或者全部失败,这个原子性是从最终结果来看的,从最终结果来看这个过程是不可分割的。

一致性(Consistency)
一个事务必须使数据库从一个一致性状态变换到另一个一致性状态。
首先回顾一下一致性的定义。所谓一致性,指的是数据处于一种有意义的状态,这种状态是语义上的而不是语法上的。最常见的例子是转帐。例如从帐户A转一笔钱到帐户B上,如果帐户A上的钱减少了,而帐户B上的钱却没有增加,那么我们认为此时数据处于不一致的状态。
从这段话的理解来看,所谓一致性,即,从实际的业务逻辑上来说,最终结果是对的、是跟程序员的所期望的结果完全符合的

隔离性(Isolation)
一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

这里先提一下事务的隔离级别:
读未提交:read uncommitted
读已提交:read committed
可重复读:repeatable read
串行化:serializable

持久性(Durability)
一个事务一旦提交,他对数据库中数据的改变就应该是永久性的。当事务提交之后,数据会持久化到硬盘,修改是永久性的。[2]

对于事务,目前知道这么多久已经足够。对于事物,更多的是了解事务的定义,以及事务的四个特性即可。其他的在本文中暂时不涉及更多。

2,什么是共享锁和排他锁

上面已经提到,数据库可能存在脏读、不可重复读和幻读的问题。而数据库里有两种锁,共享锁和排他锁,可以解决这些问题,通过控制是否施加锁,施加什么种类的锁和控制释放锁的时机,就组成了下面要提的4种事务隔离级别。那么我们先看看什么是共享锁和排他锁吧。

共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是持有共享锁的事务都只能读不能修改。

排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。

共享锁 很好理解,就是多个事务只能读数据不能改数据。

排他锁 指的是一个事务在一行数据加上排他锁后,持有排他锁的事务可以写入和读取数据,其他事务不能再在其上加其他的锁,也被排他锁锁住的数据无法被其他事务读取和写入。

注:
mysql InnoDB引擎默认的修改数据语句,update,delete,insert 都会自动给涉及到的数据加上排他锁,select 语句默认不会加任何锁类型。
如果加排他锁可以使用select …for update 语句,加共享锁可以使用select … lock in share mode语句。所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制。[3]

3,什么是读未提交、读已提交、可重复读、串行化,以及它们解决了什么问题(脏读、不可重复读、幻读)

读未提交
在读未提交的事务隔离级别下,不存在加锁的机制,因此一个事务写入完成但是尚未提交的脏数据会被别的事务读到,而这些数据可能在写入后删除(回顾事务的定义,事务是最小的执行单元,代表一批操作,可以包含多条sql)或回滚,这些未提交的数据一般视为脏数据,也造成了所谓的脏读

读未提交,存在脏读、不可重复读、幻读的问题。

读已提交
在读未提交中,已经交代了,读取另一个事务正在写入的数据,叫做脏读。而读已提交,则解决了脏读的问题。

在读已提交的事务隔离级别下,在写入数据时,会施加一把排他锁,直到事务结束后释放。在读取数据时,会对读取的数据施加共享锁,在读取完毕后释放(注意是读取完毕,不是事务结束哦。这是它和可重复读最大的区别)

由上的排他锁定义可知,持有排他锁的事务不允许其他事务施加共享锁。而读已提交在度数据时需要施加共享锁,这样在写入的数据,就被持有排他锁的事务独占,直到这个事务结束。这样也就排除了脏数据被读到的可能性,也就解决了脏读的问题。就像读已提交与读未提交的名字一样,它们的名字,正显示了两者在处理数据问题上的区别。

可重复读
正如可重复读的名字所示,可重复读在读已提交的基础上解决的问题就是不可重复读(说起来好像废话)。那先看下什么是不可重复读吧。

所谓不可重复读,简单的讲,就是在同一个事务中,对于同一行数据,先后两次读到的数据是不一致的,因而叫做不可重复读。

为什么会出现不可重复读的问题呢,从上面的读已提交上看,共享锁在数据读完就释放了,因而在给了写入数据的可乘之机,当写入数据以后,再次读取数据,此时,虽然还在同一事务中,读到的数据却是不一样的。

由此,可重复读给出的解决办法就是将读取数据的共享锁的释放时机由读取完毕改为事务结束,其他的与读已提交均一致。这就意味着,在读完数据以后,由于共享锁要等到事务结束后再释放,写入数据(在可重复读和读已提交中意味着对数据施加排他锁,排他锁和共享锁不可能同时存在)也就变得不可能了。因此不可能出现第二次读取数据时数据已被修改的不可重复度问题。

串行化
串行化隔离级别最高,同时它也解决了所有的问题,包括幻读。那先了解一下什么是幻读。

mysql的幻读指的是:在一个事务中,前后两次查询同一范围的时候,查询到了前一次没有出现的行。[4]因为新出现的数据,好像因眼花而出现的幻觉一样,所以被称为幻读。

幻读与不可重复读的区别在于:
不可重复读是在同一事务中第二次读时数据被修改,重复读取的数据不一致,针对的是某一条数据;
而幻读则是在同一事务中第二次读时出现了新增的数据或删除的数据,针对的是某次范围查询查出的数据的数据有增减。

串行化给出的解决办法是范围锁,我个人理解为在可重复读的基础上,扩大到事务所影响的所有数据上去加共享锁或者排他锁。这样就保证了一个范围内的数据不会被其他事务新增或删除进而出现数据的幻读[6]。

注意,串行化不是锁表,有博友验证过,并不是锁住整张表[5]。

对于隔离级别的讲述就先到这儿。mysql的学问也是博大精深,细想还会有很多问题,如串行化是如何实现对一个范围的锁的等问题。这些问题就留待以后有空再去写篇博客吧。

4,总结

这篇文章基本讲述了隔离级别和他们解决了什么问题,以及他们用了什么手段去解决问题。对于理解隔离级别有很好的帮助,不然只记隔离级别和它们解决了什么问题是很容易忘记的,只有理解了以后才记得更深刻,而且解决问题的思路也挺有意思的。

参考资料:
【1】,中高级Java程序员,你不得不掌握的基本功,挑战20k+
【2】,MySQL事务【详解-最新的总结】
【3】,读懂mysql读锁(共享锁)与写锁(排他锁)
【4】,MYSQL的幻读和我们平常说的幻读有什么区别?
【5】,mysql 串行化锁表的误区验证
【6】,mysql数据库事务隔离级别和对应的锁机制

06-23 00:20