文章前传:
前几天闲来无事参加了国内某大型互联网公司的面试,作为候选人一路勇敢闯关,最后一关还是被人kick out了。审判日终面,考官看面相应该是个90后,面试我这个80后,我多少感受到了一些来至内心深处并隐藏起来的歧视。上来第一个问题让我阐述mysql 和 oracle RR隔离级别的实现。我顿时有点蒙圈,毕竟平日里工作主要是产品和技术的应用研究,原理这样的类似学术研究已经不多了,若在早几年的话,还是有把握回答的。我给的答案自然不是连续的,有逻辑性的。今天有空把问题整理一下发出来,一则整理认知,二则普及一下,我尽量以最简单的语言来阐述。
从实用角度出发-理解隔离级别
什么是数据库的read场景呢?
==>有这样一张表如上图
脏-读
==>2个事物先后发生,事物1居然能读到事物2没提交的内容。
更新丢失-读
==>由于时间顺序原因,最终事物1得到了自己的结果,而事物2把钱弄丢了,原因是事物2比事物1早提交了!
非重复-读
==>事物1在两个时间点读到的内容居然不一样,期间事物2进行了更新
幻像-读
==>事物1在两个时间点读到的记录条数不一样,因为事物2在两次查询期间进行了插入或删除
什么是事物隔离级别
未提交读,这是最低的隔离级别,意味着当前事物可以从其它事物读到未提交改变。
处在未提交读隔离级别的事物都可能出现在4种读场景中(脏读、更新丢失读、非重复读、幻想读)
提交读,事物只能读到提交的事物,即提交读隔离级别仅仅防止脏读现象,发生,无法保证更新丢失读、非重复读、幻想读的场景。
重复读,事物会持有读锁和写锁,即重复读隔离级别防止非重复读场景发生,简单理解就是事物1查询的时候,都不允许另一个事物2对事物1 进行变更。重复读隔离级别无法保证幻想读场景。
序列读,该隔离级别是最高的级别,事物持有读锁和写锁之外,还会加上范围锁定,这样的话在事物处理的有效记录范围内,幻读场景不可发生。
==>已上可见图表,读者们一定要区分场景 和 隔离级别,隔离级别就是针对场景而存在的。
从原理角度出发-理解隔离级别实现
事物隔离级别本质上就是防止ANSI定义的几种事物使用场景,具体的隔离级别和事物场景已经在上文中给出。其实呢这些隔离级别和事物场景没有准确而完全的统一定义。不同的数据库产品有着各自的实现。下面我们分别对市场上的一些主流产品的隔离级别实现做个分析。
共性知识
众所周知隔离级别的实现底层都是凭借着lock实现,各种类别的锁。
简单概念解释
Item lock或 Row lock:锁定访问中的行,防止脏读
Predicate lock(gap lock):锁定一个搜索的范围,如果是全表扫描,那么将锁定整个表防 止幻像读
Short duration:在一个语句执行之后释放
Long duration:在一个事物处理完毕之后释放
==>以上,可见概念中提及锁操作组合之后便实现了事物的隔离级别。注:S代表共享锁;X代表独占锁。所有的写操作必须是X locks+Long duration的组合,若使用X locks+Short duration,那么势必会产生脏写的现象,就是说事物1已经写入但还没提交的数据会被事物2覆盖掉。
针对所有的写入行为会有3种组合的情形:
Short duration + item S locks :该情形是如果一个读的操作发现一个行正在被修改(X lock),那么读操作会在S lock上锁定,写入的事物结束,读锁 S lock释放。
Long duration + item S locks:如果一个写入的事物发现一个读操作(S lock),那么写入的事物会在X lock上锁定,读事物结束,写锁 X lock释放。
Long duration + predicate or table S locks:当一个基于范围写入事物遇到一个基于范围的读事物(predicate S locks )时候,那么写入的事物会被锁定,读取事物结束,写入事物可以继续。
==>以上,纵向列表示场景,其中fuzzy read= non-repeatableread(非重复-读),横向列表示基于锁的隔离级别。
综上知识和概念,出于性能的考虑每个产品都是基于lock实现的隔离级别。更进一步使用MVCC+LOCK的方法,读不会被锁。这是一种非常流行的实现方法哦。
Oracle隔离级别的实现
1、依据官方资料,oracle仅仅支持read committed 、 serializable isolation 、READ ONLY isolation 隔离级别。
2、为了实现MVCC,oracle内部使用scn作为依据,事物提交的时候scn会发生变化,特别地一个读操作也会获取最新的scn哦。在oracle内部mvcc的实现把数据块分成了两类:current block–最新的+持久的数据块;Consistent read blocks–基于scn快照的一致性数据块。
3、oracle 的read committed隔离级别是每个语句都要获取最新的scn,且所有的读操作都是基于快照的读,写入需要row lock。两个写事物操作同一行数据的时候,互相不会阻止对方的操作,当第二个事物获取到需要的行锁之后更新操作成功,基于上面写入的方式,根据事物提交的先后会出现lost update的情况。该隔离级别是与语句级别的读一致性。
4、oracle 的serializable isolation隔离级别依旧是所有的读请求是基于scn快照的,写操作也同样需要row lock。两个写事物操作同一行数据的时候,互相不会阻止对方的操作,当第二个事物获取到需要的行锁之后不是立即做更新,而是检查当前事物的scn和要操作的行的最新scn,如果最新更新的行的scn大于当前事物的scn,那么当前事物报错,这样就避免了lost update的情况发生了。该隔离级别是事物级别的读一致性。
小结:oracle read committed实现了语句的读一致性,避免了脏读的现象发生,读永远不会被阻塞,无法避免丢失更新读、非重复读、幻象读等现象发生。Oracle serializable 实现了事物的读一致性,避免了脏读、丢失更新读、非重复读、幻象读等现象发生,并且读永远不会被阻塞。
Mysql(INNODB)隔离级别的实现
1、根据官方资料,innodb支持ANSI定义的4种隔离级别。
2、Mysql 的Serializable isolation,这种事务的隔离级别下,所有select语句都会被隐式的转化为select … in share mode,如果有未提交的事务正在修改某些行,所有读取这些行的select都会被阻塞住。注意比较:同样是 Serializable isolation隔离级别,oracle的读是不会阻塞的,mysql 的读写都会阻塞。
3、 Mysql 的Repeated Readisolation,在该隔离级别下普通的读都使用mvcc方式,即不加锁定的无阻塞读取。相对于select for update、delete、update等操作,若使用唯一索引,那么使用item(row) lock;若不使用唯一索引进行范围扫描,那么使用 Predicate lock,mysql里的昵称叫gap lock。基于以上使用锁的方式,mysql在此隔离级别防止non-repeableread 现象发生。注意:oracle 没有这个隔离级别哦,innodb的RR隔离级别依然没有防止lost update现象出现。
4、Mysql 的read committed隔离级别和oracle的方式一致的,不需要单独解释了。
总结:一般在使用mysql的场景中没企业或组织使用Serializable isolation和uncommitted isolation,因为Serializable isolation读和写都互相阻塞,会大大影响并发;uncommitted isolation因为会读取脏数据,会大大影响数据库的一致性。read committed isolation和oracle的使用机制是一致的,所以被广泛采用。较为鸡肋的是Repeated Read isolation,mysql使用了一套自己开发的锁来实现,能防止脏读、非重复读、幻象读现象,不能防止丢失更新读现象。相比read committed,Repeated Read降低了并发能力,换来的仅仅是防止非重复读现象有点得不偿失。