事务隔离级别在数据库的具体实现原理

本文参考链接  数据库的事务隔离级别总结   数据库事务、事务隔离级别以及锁机制详解

事务并发可能引起的问题 脏读 幻读 不可重复读 

举例:https://blog.csdn.net/lululove19870526/article/details/78480135

https://blog.csdn.net/seeker520/article/details/80398900

上文讲到事务的四大特性 acid 那么数据库如何实现事务的特性呢
本文内容

目录

InnoDB引擎的事务实现

数据库隔离级别实现

互联网项目中MySQL用什么事务隔离级别

MySQL间隙锁

RC与RR在锁方面的区别

如何避免脏读、不可重复读、幻读。

mysql innodb引擎的锁机制(悲观锁)

mvcc(乐观锁)

快照读 和当前读 


InnoDB引擎的事务实现

InnoDB是mysql的一个存储引擎,大部分人对mysql都比较熟悉,这里简单介绍一下数据库事务实现的一些基本原理,在本地事务中,服务和资源在事务的包裹下可以看做是一体的。
在这里插入图片描述
       而事务的ACID是通过InnoDB日志和锁来保证。事务的隔离性是通过数据库锁的机制实现的,持久性通过redo log(重做日志)来实现,原子性和一致性通过Undo log(回撤日志)来实现
        Undo Log的原理很简单,为了满足事务的原子性,在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为Undo Log)。然后进行数据的修改。如果出现了错误或者用户执行了roll back语句,系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态。
        和Undo Log相反,Redo Log记录的是新数据的备份。在事务提交前,只要将RedoLog持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是RedoLog已经持久化。系统可以根据Redo Log的内容,将所有数据恢复到最新的状态。

数据库隔离级别实现

标准SQL规范中,定义了四个事务隔离级别:RU RC RR S

       各类流行的数据库都实现了一些SQL标准中的事务隔离级别,但是他们的实现也是极其不一样的。

       Oracle仅仅实现了RC 和 SERIALIZABLE隔离级别默认采用RC隔离级别,解决了脏读。但是允许不可重复读和幻读。其SERIALIZABLE则解决了脏读、不可重复读、幻读。

       MySQL默认采用RR隔离级别,SQL标准是要求RR解决不可重复读的问题,但是因为MySQL采用了间隙锁(gap lock),所以实际上MySQL的RR隔离级别也解决了幻读的问题。那么MySQL的SERIALIZABLE是怎么回事呢?MySQL的SERIALIZABLE采用了经典的实现方式,对读和写都加锁。

互联网项目中MySQL用什么事务隔离级别

Mysql默认的事务隔离级别是可重复读(Repeatable Read),那互联网项目中Mysql也是用默认隔离级别,不做修改么? OK,不是的,我们在项目中一般用读已提交(Read Commited)这个隔离级别! 居然是读已提交。

我们先来思考一个问题,在Oracle、SqlServer中都是选择读已提交(Read Commited)作为默认的隔离级别,为什么Mysql不选择读已提交(Read Commited)作为默认隔离级别,而选择可重复读(Repeatable Read)作为默认的隔离级别呢?

我们先明白一点!项目中是不用读未提交(Read UnCommitted)和串行化(Serializable)两个隔离级别,原因有二: 采用读未提交(Read UnCommitted),一个事务读到另一个事务未提交读数据,这个不用多说吧,从逻辑上都说不过去!采用串行化(Serializable),每个次读操作都会加锁,快照读失效,一般是使用Mysql自带的分布式事务功能时才使用该隔离级别!也就是说,我们该纠结都只有一个问题,究竟隔离级别是用读已经提交呢还是可重复读? 接下来对这两种级别进行对比,讲讲我们为什么选读已提交(Read Commited)作为事务隔离级别!

MySQL间隙锁

当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(GAP LOCK)。举例来说,假如user表中只有101条记录,其empid的值分别是 1,2,…,100,101,下面的SQL:

select * from  user where user_id > 100 for update;

这是一个范围条件的检索且要求加上排他锁,InnoDB不仅会对符合条件的user_id值为101的记录加锁,也会对user_id大于101(这些记录并不存在)的“间隙”加锁。

InnoDB使用间隙锁的目的,一方面是为了防止幻读(为了防止幻读去锁表则影响太大,会影响效率),以满足相关隔离级别的要求,对于上面的例子,要是不使用间隙锁,如果其他事务插入了user_id大于100的任何记录,那么本事务如果再次执行上述语句,就会发生幻读;另外一方面,是为了满足其恢复和复制的需要(发生幻读时的binlog,如果直接拿到备库去执行会发生了主备数据不一致的严重问题)。

很显然,在使用范围条件检索并锁定记录时,InnoDB这种加锁机制会阻塞符合条件范围内键值的并发插入,这往往会造成严重的锁等待。因此,在实际应用开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件;当然,对一条不存在的记录加锁,也会有间隙锁的问题。

间隙锁在InnoDB的唯一作用就是防止其它事务的插入操作,以此来达到防止幻读的发生,所以间隙锁不分什么共享锁与排它锁。如果InnoDB扫描的是一个主键、或是一个唯一索引的话,那InnoDB只会采用行锁方式来加锁,而不会使用Next-Key Lock的方式,也就是说不会对索引之间的间隙加锁。

要禁止间隙锁的话,可以把隔离级别降为读已提交,或者开启参数innodb_locks_unsafe_for_binlog。

RC与RR在锁方面的区别

1、RR要用到间隙锁,而RC则没有间隙锁。因为MySQL的RR需要间隙锁来解决幻读问题。而RC隔离级别则是允许存在不可重复读和幻读的。所以RC的并发一般要好于RR;在RR隔离级别下,存在间隙锁,导致出现死锁的机率比RC大的多;
2、 RC 隔离级别,通过 where 条件过滤之后,不符合条件的记录上的行锁,会被释放掉,但是RR隔离级别,即使不符合where条件的记录,也不会释放行锁和间隙锁,所以从锁方面来看,RC的并发应该要好于RR;
3、RC隔离级别时,事务中的每一条select语句会读取到他自己执行时已经提交了的记录,也就是每一条select都有自己的一致性读ReadView; 而RR隔离级别时,事务中的一致性读的ReadView是以第一条select语句的运行时,作为本事务的一致性读snapshot的建立时间点的,只能读取该时间点之前已经提交的数据。

如何避免脏读、不可重复读、幻读。


1、设置隔离级别
2、悲观锁:传统关系型数据库的行锁、表锁、读锁、写锁。就是这种锁机制。 间隙锁
3、乐观锁;使用版本号或则cas算法。
      cas: compare and swap(比较和交换):著名无锁算法。无锁编程:不使用锁的情况下实现多个线程之间的变量同步,也就是在没有阻塞的情况下实现线程变量同步,
        CAS设计到三个操作:
            读取需要操作的值V
            进行比较的值A
            虚拟写入的值B
            当且仅当V的值等于A时,cas将需要写入的新值B来更新V,否则不执行任何操作。一般情况下是一个自旋操作,不断重试。
乐观锁适用于写比较少的情况(并发量大/多读场景)即冲突少发生。这样可以省去锁的开销,加大系统的吞吐量;如果多写的情况,一般会产生冲突,这样导致上层应用不断retry,反而降低了系统性能,所以悲观锁比较适合。


mysql innodb引擎的锁机制(悲观锁)


    innodb支持事务支持行锁,表锁,myisam不支持事务,只支持表锁。
1)锁的使用
共享锁:允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁。(可以读不可以写)
排它锁:允许获得锁事务进行跟新,拒绝其他事务获得锁。
意向共享锁 锁表 表明加锁的类型 先对表加锁,再决定对具体行还是表加锁。
意向排他锁 锁表 表明枷锁的类型
注意:意向锁是数据库主动加的不需要手动处理。
2)锁的粒度:行锁、表锁、叶锁(间隙锁)
行锁是对索引列或则主键加锁。

通过加锁控制可以保证数据的一致性,但是同样一条数据,不能读写并发(排它锁的原因)
这时就要引入 数据多版本控制来实现读写并发。


mvcc(乐观锁)

这项技术使得InnoDB的事务隔离级别下执行一致性读操作有了保证,换言之,就是为了查询一些正在被另一个事务更新的行,并且可以看到它们被更新之前的值。这是一个可以用来增强并发性的强大的技术,因为这样的一来的话查询就不用等待另一个事务释放锁。

 

数据多版本实现的原理是:

1,写任务发生时,首先复制一份旧数据,以版本号区分

2,写任务操作新克隆的数据,直至提交

3,并发读的任务可以继续从旧数据(快照)读取数据,不至于堵塞

快照读 和当前读 


select是快照读    其他 u d i  selet..lock in share mode  select for update 当前读
保证同一个事务中读到的数据是相同的不会脏读   grap lock保证不会幻读

实现原理  innodb加版本号

总结:排它锁 是 串行执行
        共享锁 是读读并发
        数据多版本:读写并发

总结:

数据库并发问题,主要通过设置事务隔离级别来解决,而事务隔离级别一般则通过锁机制的实现;

MySQL默认隔离级别(RR)使用MVCC+锁混合的模式来解决脏读、不可重读、幻读等问题。

MySQL(Innodb引擎)下

默认的事务级别为:可重复读级别(RR);(可通过设置进行更改)

默认锁级别为:行锁;(可通过设置进行更改)

Where筛选条件中使用索引字段的,加的是行锁;不是使用索引字段筛选的,加的是表锁。

意向共享锁和意向排它锁是数据库主动加的,不需要我们手动处理;

对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁;

对于普通SELECT语句,InnoDB不会加任何锁;(可以自己手动上锁)


 


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章