大家好,又见面了,我是你们的朋友全栈君。
网上大多数关于隔离级别的文章都是讲了事务中的问题以及隔离级别可以解决的问题,我这次想看看数据库底层是如何实现隔离级别的。
不过还是先来回顾一下隔离级别以及可能发生的问题。
1.脏读:指的是一个事务的读操作读到了另一个未提交的事务修改的值。比如下面的场景:
脏读的问题是,读到的值可能会被回滚,那么这个值就是失效的,不能继续使用,否则会有一致性问题。
2.不可重复读:指的是一个事务读了同一个值两次,但是两次的值不同,因为中间另一个事务修改了这个值,比如下面的场景:
t2事务是已提交的,所以t1的第二次读取是可以读到的。
3.幻读:仍然指的是一个事务中读了两次,结果不同,但是与不可重复读不同的是,这里不同是因为别的事物做了插入操作,而且读的条件是一个范围的条件,这样第二次会多读到一条数据,比如下面的例子:
相应的有四种隔离级别,不同程度的解决了上面的问题:
可以看到隔离界别越高,解决的问题就越多,但是并行化程度也越低,效率也越低。
那么数据库是如何实现隔离界别呢?下面以mysql innodb引擎来说下。事务隔离性,本质上解决事务的读写冲突的,在mysql中,读操作被实现为一种叫做“非锁定读取“的技术。什么意思,我们通常会说数据库里有共享锁和排它锁,读操作就是共享锁,只有共享锁和共享锁是非互斥的,那么如果一个读操作要在一个正在被修改的数据上进行,那么是无法加上获取该行对象的共享锁的,这便是数据库串行化隔离界别的实现方式,但是在其余的隔离界别下,我们不需要这么严格的隔离,我们允许了读操作可以在正在被修改的数据上即加上了排它锁的行数据上进行,这就是所谓的”非锁定读取“概念。
那么,读取的结果是什么?这个还需要明白另一点,就是我们数据库MVCC和undo日志,我们每一次的修改操作,并不是直接对行数据进行操作,比如我们设置id为3的行的A属性为10,并不是直接修改表中的数据,而是新加一行,同时数据表其实还有一些隐藏的属性,比如每一行的事务id,所以每一行数据可能会有多个版本,每一个修改过它的事务都会有一行,并且还会有关联的undo日志,表示这个操作原来的数据是什么,可以用它做回滚。那么为什么要这么做?因为如果我们直接把数据修改了,那么其他事务就用不了原先的值了,违反了事务的一致性。那么一个事务读取某一行的数据到底返回什么结果呢?取决于隔离级别,如果是Read Committed,那么返回的是最新的事务的提交值,所以未提交的事务修改的值是不会读到的,这就是Read Committed实现的原理。如果是Read Repeatable级别,那么只能返回发起时间比当前事务早的事务的提交值和比当前事务晚的删除事务删除的值。这其实就是MVCC方式。所以在第二个例子中,t2事务发生在t1后面,所以它更新的值是不会被t1读取到的,所以t1两次读取的值相同,也就是可以重复读。
关于可串行化的隔离界别,也很简单,严格的按照加锁协议来就可以,该级别可以避免上述所有的问题。
在oracle等数据库中,为了避免幻读,只能采用可串行化隔离级别,但是在innodb引擎中,在Repeatable Read级别也可以实现,主要是一种叫做”Next Locking“的技术,一种特殊的锁。
正常的锁一般是锁一行,称为是Record锁,而”Next Lock“则是锁一个范围内的行,比如说第三个例子中,t1事务第一次查询的是10到30之间的数据,那么最终这些数据都要被上锁,那么后面t2事务想要插入数据就不行了,会被拒绝,再往后,t1的第二次读取就会和第一次一样了。innodb采用这样的办法,就在repeatable read级别解决了幻读的问题。
最后总结一下innodb里面的锁的互斥,在可串行化级别,严格按照加锁协议,只有共享锁可以并行,其他全部阻塞。但是在其他的隔离级别,就放宽了很多。不过写-写是肯定不行的,读-读是肯定行的,读-写也是可以的,只不过读到什么内容就由MVCC来决定,由具体的隔离级别决定,写-读一般都可以,只有在启用了”Next Locking“的情况下会被拒绝。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/141390.html原文链接:https://javaforall.cn