事务隔离级别的学习

写在前面:

这个博客记录的是自己学习过程和理解,有些细节可能写的不见得准确,想了解概念,谷歌可以搜出很多不错的博文去学习.我把它记下来,则是为了记录学习的过程,因为有些知识是需要自己创造实践的机会才能真正理解其中的细节.

先贴概念

由ANSI/ISO定义的SQL-92标准定义的四种隔离级别

  • 读取未提交内容/脏读(READ UNCOMMITTED/Dirty Read):无效数据的读出 . SELECT语句是在无锁的模式下运行,一个事务可以读到另外一个事务的未提交的数据。

  • 提交读(READ COMMITTED):一个事务不能读到另外一个事务未提交的数据。在这种隔离级别下,对于更新语句来说(SELECT FOR UPDATE,UPDATE和DELETE),Innodb只会对where条件中索引能覆盖到的行进行上锁不会上gap锁和next key lock,所以就避免不了不可重复读和幻读

  • 可重复读(REPEATABLE READ):是Innodb默认的隔离级别。它使用了gap locks或者next-key locks避免不可重复读的现象,但是仍然避免不了幻读。如果SELECT FOR UPDATE,UPDATE以及DELETE的where条件能用到唯一索引,INNODB只会对唯一索引能覆盖到的行上行锁;否则就上gap locks或者Next-key locks。

  • 串行化(SERIALIZABLE):这是最高的隔离级别,避免了幻读。如果auto commits不启用,则innodb把每个select转换成select ... lock in share mode(使用共享锁);如果启用,则每个SELECT语句也是事务。

隔离解别脏读不可重复读幻读
Read UncommittedYYY
Read CommittedNYY
Repeatable(default)NNY
SerializableNNN

个人的理解和实践

一部分归纳可能并不准确,望斧正

这里先介绍几个概念

锁的介绍

共享锁(Share lock)

行锁的一种,共享锁又称读锁,在对任何数据进行读操作之前要申请并获得共享锁 ,若事务1对数据对象A加共享锁,则事务1可以读A但不能修改A(其他事务只能再对1加共享锁,而不能加排他锁,),其他事务只能再对A加共享锁,而不能加排他锁,直到事务1释放A上的共享锁。这保证了其他事务可以读A,但在事务1释放A上的S锁之前不能对A做任何修改。 共享锁的英文名是Share Locks,其实如果叫做读锁就好理解多了(毕竟字面上'共享'这个词好像没有这东西就不能改的意思嘛) 在查询语句后面增加LOCK IN SHARE MODE,Mysql会对查询结果中的每行都加共享锁

共享锁试验例子
session1session2
begin tran<br>select * from table1 holdlock where B='b2'<br>waitfor delay '00:00:30'<br>commit tranbegin tran<br>select A,C from table1 where B='b2'<br>update table1 set A='aa' where B='b2'<br>commit tran

排他锁(Exclusive Locks)

行锁的一种,排他锁又称写锁,互斥锁,独占锁(名字可真多,但都是一个意思嘛)。在进行写操作之前要申请并获得排他锁,若事务1对数据对象A加上排他锁,事务1可以读A也可以修改A,其他事务不能再对A加任何锁,直到1释放A上的锁。这保证了其他事务在1释放A上的锁之前不能再读取和修改A。 排他锁英文名字是Exclusive Locks(我英语不好,不太明白为啥叫X锁不叫E锁),这个字面好理解,排除其他的事务,只能自己读写,虽然叫写锁,实际上事务1 rw的权限都是有的 在查询语句后面增加FOR UPDATE,Mysql会对查询结果中的每行都加排他锁 对于insert、update、delete,InnoDB会自动给涉及的数据加排他锁(X)

两段锁协议(Two-Phase Locking――2PL)

两段锁协议是指每个事务的执行可以分为两个阶段:生长阶段(加锁阶段)和衰退阶段(解锁阶段)。

加锁阶段

在该阶段可以进行加锁操作。在对任何数据进行读操作之前要申请并获得共享锁,在进行写操作之前要申请并获得排他锁。加锁不成功,则事务进入等待状态,直到加锁成功才继续执行。

解锁阶段

当事务释放了一个封锁以后,事务进入解锁阶段,在该阶段只能进行解锁操作不能再进行加锁操作。

两段封锁法可以这样来实现:事务开始后就处于加锁阶段,一直到执行ROLLBACK和COMMIT之前都是加锁阶段。ROLLBACK和COMMIT使事务进入解锁阶段,即在ROLLBACK和COMMIT模块中DBMS释放所有封锁。

意向锁

为了方便检测表级锁和行级锁之间的冲突,减少加锁时封锁检查的工作量,支持多粒度的锁定,基于两种基本的锁类型,可以派生出如下两种意向锁: 概念有点抽象,简单的一下为什么要意向锁: 意向锁是为了避免逐行检查行是否上锁,解决行锁和表锁之间的矛盾 比如事务1要在一个表上加共享锁,如果表中的一行已被事务2加了排他锁,那么该锁的申请也应被阻塞。如果表中的数据很多,逐行检查锁标志的开销将很大,系统的性能将会受到影响。 如果表中记录1亿,事务1把其中有几条记录上了行锁了,这时事务2需要给这个表加表级锁,如果没有意向锁的话,那就要去表中查找这一亿条记录是否上锁了。如果存在意向锁,那么假如事务1在更新一条记录之前,先加意向锁,再加独占锁,事务2先检查该表上是否存在意向锁,存在的意向锁是否与自己准备加的锁冲突,如果有冲突,则等待直到事务1释放,而无须逐条记录去检测。事务2更新表时,其实无须知道到底哪一行被锁了,它只要知道反正有一行被锁了就行了。 意向锁是InnoDB自动加的,不需要用户干预

意向共享锁(IS)

事务打算给数据行加共享锁,事务在给一个数据行加共享锁前必须先给该表加上IS锁。

意向独占锁(IX)

事务打算给数据行加排他锁,事务在给一个数据行加排他锁前必须先给该表加上IX锁。 意向锁主要目的是为了在一个事务中揭示下一行将被请求的锁类型。如果没意向锁,需要表锁时,要一行行检查某个表是否发生了行锁,进而判断能否表锁成功,如果有了意向锁,不用一个个去检查,直接从表的层次就可判断。

  1. 意向锁为表级锁,但表示的是事务正在操作某一行记录
  2. 意向锁之间不会发生冲突,冲突检测是在加行锁时发生

表锁(Table Lock)

对整个表加锁,影响表的所有记录。通常用在DDL语句中,如ALTER、DROP、TRUNCATE等。

行锁(Row Lock/record lock)

对一行记录加锁(准确的说应该是对索引加锁而非记录本身),只影响一条记录。通常用在DML语句中,如INSERT、UPDATE、DELETE等。

间隙锁(gap lock )

锁住某一段范围中的记录 ,在索引记录之间的间隙中加锁,或者是在某一条索引记录之前或者之后加锁,并不包括该索引记录本身,去解决幻读和获得基于语句模式下复制的一致性 gap

next key lock

是Record lock和gap lock的结合,即除了锁住记录本身,还要再锁住索引之间的间隙 行锁和GAP锁结合形成的的Next-Key锁共同解决了RR级别在写数据时的幻读问题

悲观锁

正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

在悲观锁的情况下,为了保证事务的隔离性,就需要一致性锁定读。读取数据时给加锁,其它事务无法修改这些数据。修改删除数据时也要加锁,其它事务无法读取这些数据

乐观锁

相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。

而乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

要说明的是,多版本并发控制(MVCC)的实现没有固定的规范,每个数据库都会有不同的实现方式

事务隔离级别的理解

读取未提交内容/脏读(READ UNCOMMITTED/Dirty Read)

这个事务隔离级别任何操作都不会加锁

提交读(READ COMMITTED)

在RC级别中,数据的读取都是不加锁的,但是数据的写入、修改和删除是需要加锁的,这样可以避免脏读问题 如果where的条件是没有索引的,存储引擎层面就会将所有记录加锁后返回,再由MySQL Server层进行过滤。但在实际使用过程当中,MySQL做了一些改进,在MySQL Server过滤条件,发现不满足后,会调用unlock_row方法,把不满足条件的记录释放锁 (违背了二段锁协议的约束)。 虽然mysql做了优化,我们还是尽量使用索引避免锁住不相关的记录

提交读 会引起死锁么?

举个栗子:如果事务1和事务2都是插入了同一条记录,因为行锁的存在,有一个必定抛异常.虽然抛了异常,但不影响失败的那个事务得到了成功事务释放掉的排它锁.如果这时不做回滚,这时突然冒出个事务3 读取记录,这就会导致死锁现象 所以代码里如果是因为主键冲突抛出了异常,是要做回滚来释放独占锁的 引用别人的表格加以说明 | session1 | session2 | session3 | | :-- | :-- | :-- | | Session_1获得for update的共享锁:<br>mysql> select actor_id, first_name,last_name from actor where actor_id = 201 for update;| 由于记录不存在,session_2也可以获得for update的共享锁:<br>mysql> select actor_id, first_name,last_name from actor where actor_id = 201 for update; | | | Session_1可以成功插入记录:<br> mysql> insert into actor (actor_id,first_name,last_name) values(201,'Lisa','Tom'); | | | | | Session_2插入申请等待获得锁:<br>mysql> insert into actor (actor_id,first_name,last_name) values(201,'Lisa','Tom'); | | | Session_1成功提交: <br>mysql> commit; | | | | | Session_2获得锁,发现插入记录主键重,这个时候抛出了异常,但是并没有释放共享锁: <br>mysql> insert into actor (actor_id,first_name,last_name) values(201,'Lisa','Tom'); | | | | | Session_3申请获得共享锁,因为session_2已经锁定该记录,所以session_3需要等待: <br>mysql> select actor_id, first_name,last_name from actor where actor_id = 201 for update; |

可重复读(REPEATABLE READ)

这是MySQL中InnoDB默认的隔离级别。 在同一个事务内的查询都是事务开始时刻一致的 对选定对象的读锁和写锁一直保持到事务结束,但不要求“范围锁”,因此可能会发生幻读 带唯一搜索条件使用唯一索引的SELECT ... FOR UPDATE, SELECT ... LOCK IN SHARE MODE, UPDATE 和DELETE语句只锁定找到的索引记录,而不锁定记录前的间隙 用其它搜索条件,这些操作采用next-key锁定,用next-key锁定或者间隙锁定锁住搜索的索引范围,并且阻止其它用户的新插入。 select ... for update 是行级锁,也是悲观锁

串行化(SERIALIZABLE)

每次读都需要获得表级共享锁,写加排他锁,读写互斥。使用的悲观锁

读写现象的介绍

快照读

简单的select操作,没有lock in share mode或for update,快照读不会加任何的锁,而且由于MySQL的一致性非锁定读的机制存在,任何快照读也不会被阻塞。但是如果事务的隔离级别是SERIALIZABLE的话,那么快照读也会被加上共享的next-key锁

当前读(locking read)

insert,update,delete,select..in share mode和select..for update,当前读会在所有扫描到的索引记录上加锁,不管它后面的where条件到底有没有命中对应的行记录。当前读可能会引起死锁。

脏读(Dirty Read)

        事务1:更新一条数据
                         ------------->事务2:读取事务1更新的记录
        事务1:调用commit进行提交

不可重读(UNREPEATABLE READ)

名字有点抽象,不好理解,不可是不能还是不要的意思?为什么不可?我就补充一下主谓宾解释一下吧 [同一条记录]不可重[被同一个事务]读[,因为两次读取的结果可能不一样] 举个栗子:

        事务1:查询一条记录
                        -------------->事务2:更新事务1查询的记录
                        -------------->事务2:调用commit进行提交
        事务1:再次查询上次的记录

因为中间事务2做了改动,导致事务1查两次的结果是不一样的 不可重复读重点在于update和delete

可重读(REPEATABLE READ)

[同一条记录]可重[被同一个事务]读[,因为无论多少次结果都是一样滴]

幻读(phantom read )

名字有点抽象,翻译过来叫做"幻想读"(有点像游戏技能名字) 在事务执行过程中,当两个完全相同的查询语句执行得到不同的结果集。这种现象称为幻读 举个栗子:小时候看 《哈克贝利·费恩历险记》记得有个小桥段,萨莱姨妈数叉子个数时汤姆和哈克趁她不注意放回或拿走叉子,让萨莱姨妈每次数出来叉子的个数都不一样.幻读基本就是这个意思,还是用文字表示一下这现象是怎么发生的: 事务1:查询表中所有记录 -------------->事务2:插入一条记录 -------------->事务2:调用commit进行提交 事务1:再次查询表中所有记录 由于事务2(汤姆和哈克)偷摸加了一条记录(叉子),结果导致事务1(萨莱姨妈)数出的个数和上次的比多了一个,结果就让她直接懵了 幻读的重点在于insert

转载于:https://my.oschina.net/tdONEmadao/blog/742515

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值