八张图教你彻底理解数据库并发控制之隔离级别(上)

八张图教你彻底理解数据库并发控制之隔离级别(上)

数据库的隔离级别,是数据库理论中和实践中最为复杂深奥的东西,没有之一。老子经常云:“道可道,非常道”,理解起来就是:“道”这种东西十分之深奥,是不可以讲出来的,为什么呢?因为没法讲,只能自行体会,即所谓的悟。然而即便是像“道”这种神奇之极复杂之极深奥之极的玩意,只要一旦领悟,便会豁然开朗而澎湖灌顶,举一反三而触类旁通,并且从此面对世间任何事情再无迷惑之感。

圣经上也说,“你需要知道真相”,真相是这个世界上一切事物中问题中最为关键的部分,而通往真相的过程亦是不断实践,不断分析,不断思考的过程。西方的“真相”同东方的“道”讲得完全是一样东西,只是说法各异而已,通俗点来说,指的是事物的本质。

所以对于数据库隔离级别而言,也是有本质和非本质之分的。抛开繁重的细枝末节,理解其本质,是掌握数据库隔离级别的法则。而本文的目的则是以最大可能的模拟通往事物本质的过程,一步一步的找到通往数据库隔离级别真谛之“道”。

在此之前,首先得搞明白一样东西,就是所谓事务的原子性。相比起事务的其他几个性质而言,原子性是最为重要的特性,没有之一。众所周知,原子是这个世界上极为微小的东西,看不见摸不着,靠人力是无法将其一分为二,当然这里并不排除特异功能患者,如果有的话。而所谓原子性,便是将原子这种不可分割的性质形对事务一种深刻的比喻,说明了事务的不可分割性,要不就做,要不就不作,没有做一半又不做一半这种说法。如果再形象点再生动点,就是您老人家打喷嚏时不可能只打半个喷嚏,性高潮射精时也不可能只射一半的精然后硬生生的把另一半给逼回去,当然这里指的是一般的童鞋,不包括个别体格奇异者。

好的,原子性是数据库中最为深刻的内容,请记住这个结论。因为:第一,如果没有原子性,也就不存在隔离级别。第二,下文将无限用到这个概念。

数据库隔离级别产生的根本原因在于数据库访问的并发。出于性能的考虑,一些并发可能会破坏事物的原子性。即一个事务还没有完成,另一个事务又开始启动了。如果两个事物访问的资源不同,也就不会产生冲突,自然也就不会引发什么问题,但是倘若两个事物访问的资源一样,问题就来了。就好比两个雄性动物面对着只有一个磁性动物的时候,问题就出现了,但如果解决得好(大家商量好先后顺序),也就不会有什么问题。

《数据库系统原理教程》这本书明确的指出了在并发操作数据库中可能会出现的问题,同时也给出了解决或者是避免这些问题的若干“指导意见”(三级封锁协议)。当然这些仅仅是建议而已,至于各个数据库厂商是如何具体实现这些协议来控制并发问题的细节,这个我们就不得而知了。但是无论其实现的手法如何,这些都逃不出计算机基本原理的范畴,这里的范畴指的是基本的算法、数据结构、计算机系统结构原理等。所以,我们可以用这些基本原理以及上述若干“指导意见”来分析并发操作的问题,同时对解决这些问题的手段背后的原理进行分析,从而找到这些现象背后的实质,从唯物论的观点而言便是——“透过现象看本质”。

并发操作数据库可能引发的问题分别是:1、更新丢失;2、未提交的读;3、不可重复读;4、“幻象”读。其解决这四个问题的手段分别为:1、UR(为落实的读);2、CS(游标稳定性);3、RS(读稳定性);RR(可重复读)。接下来我们将对这些问题发生的现象,发生的原因,背后的实质以及解决的手段一一进行解剖,一一进行分析。

在进入具体的分析之前,我们首先要对数据库的锁机制和存储机制有个大致的了解,因为这些知识将贯穿全文的始终。这些内容如同一把锋利的武器,一旦被掌握,那么就可以上阵杀敌攻城略地所向披靡了,当然即便是杀不了人,那劈劈柴也还是没有虾米问题的。

首先是锁机制,任何事务对数据进行访问之前都要给数据进行加锁,数据库中的锁分为X锁(排他锁)和S锁(共享锁)。从理论上说,这两把锁在一切程度上保证了数据库的安全和效率。X锁一般出现在修改数据的过程中,当数据被某个事务加上X锁之后,此事务可以对数据进行任何操作,其他任何事务将不能给这个数据加任何形式的锁,也就是其他事务不能访问该数据,简单点,就是这个数据被独占了,“是我的,不是大家的,我想肿么搞就肿么搞”(X锁如是说)。而S锁一般出现在读数据的过程中,当某个事务对数据加上S锁之后,不仅本事务仅能对该数据进行读操作,而且其他事务也能对该数据加S锁读取该事务(但是其他事务不能对此加X锁进行修改),简而言之,就是这个数据是大家的,大家都可以读,但是不能修改(因为对数据修改之前是要加X锁的)。

独孤九剑大概是这个世界上最奇妙的剑法,它翻来覆去就是那么九招,但是通过这九招的各种灵活变换,却可以破尽天下一切武功。因为这九招是天下武功的基础,是精华所致。它抓住了武功的一个基本点,“天下武功,唯快不破”,以不变应万变,不管其他武功如何霸道强悍或是变化莫测,我只要比你快就行了,料敌致先,无往不胜。数据库中的锁机制中也只有X锁和S锁,这个便是解决并发问题的基础,将这两种锁灵活运用,也能根本的解决以上四种并发问题。

其次是内存机制。

如果有人要问,什么是内存机制?

说实话,我也不太懂如何给这个词做个清晰且精妙的定义,这通常是那些理论学家干的事情。从计算机系统结构的角度来说,数据库如何读取硬盘数据到内存,采用哪种换页算法将暂时访问不到的数据替换出内存,数据如何通过IO进行输入输出,内存数据如何同硬盘数据进行同步,等等,等等。这些问题由于与内存交涉较大,所以本人仅对以上内容取了个不是太难懂的名字,叫内存机制。

在谈论内存机制之前,我们先回顾一下,计算机系统结构课程的一些基本概念:

概念一:对于一般的计算机而言,由于内存较为昂贵,所以数据都是存储在硬盘,在需要用的时候再从硬盘读取到内存(当然这里指的是一般的计算机及其传统硬盘,不包括固态硬盘,也不包括将来人类技术的发展制造出可以超越目前理论的计算机系统结构),之后计算机对数据的处理,使用的都是内存中的数据,再然后,如果计算机需要处理内存中不存在硬盘中存在的数据,则需要各种神奇复杂的换页算法。

概念二:每一个进程拥有自己的上下文结构,内存空间,这个很重要,每个进程对自己内存空间中数据的绝对控制是保证数据安全的必要条件。并且每个进程自己内存空间中的数据是建立在进程的栈空间的基础上,进程存在时它存在,进程不存在时也就自动撤销了(这真是所谓的“一荣俱荣,一损俱损”啊!!!!!!)。

学过操作系统原理的童鞋应该对以上两点并不陌生,但是可能这些概念太过于基础,有的童鞋当时学习的时候并不认为这些原理有什么用(有的童鞋甚至认为网页制作等一些流行技术才是计算机的王道),对我们的生活往往没有什么影响。其实说白了,这些原理并不是用来影响我们的,基本原理之所以作为基本原理,是因为它们是现代计算机技术的基础,它影响的都是一些顶尖的计算机高手。而我们所受到的影响却是由这些顶尖高手们带来的,他们有的开创了一个时代,如微软公司的windows操作系统,有的引领着潮流,如苹果公司IOS操作系统。但即便是我们没法像比尔盖茨或者乔布斯一样精通这些原理,并在此基础上建立了丰功伟业,但是知道一下了解一下对我们而言也不是坏事:第一可以用这些基本原理来武装武装大脑,时不时拿出来吹嘘两下,故作高深的旁人面前显摆显摆,“什么,最近未使用换页算法么,这个简单嘛,知道的,指导的……”;第二可以用它们作为分析的工具,深入的了解这些计算机顶尖高手们当时是如何构建出这些伟大的产品的,当然如果第二种用法使用不当,那简直就是为第一种用法服务的了。

接下来我们将利用以上原理就尝试猜想一下数据库在并发操作中是如何加锁的原理,并符合逻辑的论证这个过程。

按照理论,我们大胆预测数据库系统对数据加锁有三种形式:

一、某进程在硬盘中对数据进行加锁,然后读取到内存中进行操作,操作完之后立即写回数据库并解锁,其他进程可以操作该数据。

二、某进程将数据从硬盘读取到内存,在内存中对数据进行加锁,之后对数据进行各种操作,最后解锁写回数据库。

三、某进程在硬盘中对数据进行加锁,然后读取至内存进行操作,操作完之后并不立即写回数据库(除非明确指定),而是根据后续操作决定是否写回数据库并解锁。(注意与形式一的区别)。

这三种加锁形式从本质上而言,符合计算机系统结构的各项原理。当然可能也会有些数据库厂商另辟蹊径,发明出完全不符合现代计算机结构的加锁形式。当然这并不是不可能,但就目前而言,确实是有点不可能。就好像现代物理和生物学理论认为人不可能像鸟一样,在空中自由翱翔,这个是由于地心引力而人的基本结构所决定的(木有翅膀,即便是有翅膀,以人体的重量和肌肉力量也无法飞起来),不以人的意志为转移。

那么,数据库系统究竟用哪种方式来对数据进行加锁呢?

我们先来看一下这几种加锁方式究竟有什么不同。

第一种加锁方式,加锁——读取——操作——写回——解锁。在这种情况下,数据库系统在硬盘中对每一条记录加两个额外的标志(X锁和S锁)以标记是否加了此种类型的锁。然后某进程在读取该数据之前,先检查该数据的标志位是否被其他进程所修改(即被其他进程加锁),如果没有,则修改该记录的标志位,同时读取到内存中进行操作,操作完毕之后立即写回数据库并将标志位修改回来。这时候另一个等待该数据的进程一旦发现标志位被改回,则可以获取该资源。

这是一种比较简单,比较原始的设计,但是非常浪费资源,所以任何一个正常的计算机高手都不会这样设计数据库系统。这种设计每一次操作都需要对硬盘进行操作,读写一次硬盘,极大的增加了通讯量和硬盘的读写次数。目前而言,在计算机CPU频率越来愈足够高内存容量越来越大的的趋势下,IO通讯速度成为了计算机的瓶颈。这种做法完全是取短补长的行为。当然,这种设计方法也不是一无是处,由于它要提取到内存的数据必须是加了锁的,所以另一方面它极大的避免了内存浪费。

第二种加锁方式,读取——加锁——操作——各种操作——……——解锁——写回。具体操作如下,某进程将数据读取至自己的内存空间,修改标志位,然后进行各种操作。如果这时另外的进程需要访问该数据,则需要看其是否有相关权限。(S锁可以被S锁访问,X锁则不能被任何锁访问)如果有,则给该数据加S锁(注意,此处只能加S锁!),然后进行访问。

这里有个问题要注意一下,根据计算机系统结构原理,每个进程都有自己的内存空间,那么进程对数据的读取便有两种方式:一、读取到自己的内存空间进行操作。二、读取到共用的内存空间进行操作。

如果采用第一种方式,那么导致的结果便是每个要访问同一个数据的进程的内存空间都会有该数据的副本。如果进程过多,数据被复制多次从而副本充斥着内存的各个角落形成数据冗余(根据任何事务都具有二义性的普遍哲学原理,数据冗余有好处也有坏处,坏处在于浪费存储空间,好处在于有利于数据恢复),这是很不科学的。所以采用第二种方式才更为合理些。

第三种加锁方式,加锁——读取——操作——各种操作——……——写回——解锁。这种加锁方式与第二种加锁方式相比,不同点仅是在读取之前加锁还是在读取之后加锁。这一点看起来似乎区别不大,但是别忘记了一句话“细节决定成败”,正是由于这个细节,可能导致效率极大的不同。

如果采用第三种加锁方式,当一个进程对数据加S锁进行查询操作时,另外一个进程也想访问该数据。那么它先访问硬盘,查看该数据是否被其他进程锁定。这时候他发现该数据已被其他线程加了N把S锁,所以他便在此基础上加上第N+1把S锁(它只能加S锁!!!!!!),然后去内存中读取该数据(数据已存在内存中了)。

注意,这里它多访问了一次硬盘,而原本这次硬盘的访问时可以被避免的,当我们用第二种加锁方式的情况下。进程先将数据读取至内存,然后增加其标志位(数据在硬盘中的存放没有单独的S锁和X锁的标志位的情况下),如果是S锁,则将S锁的标志位设为1,如果是X锁,则将X锁的标志位设为1。这时如果另一进程需要访问该数据同时进行查询操作,则现在内存查找该数据,找到之后发现该数据S锁的标志位上为N,于是该进程将数据S锁上的标志位改为N+1,从而访问该数据。

在软件界这个注重效率的行业中,第二种加锁方式无疑是最佳的。至于数据库厂商是否选择选择这种加锁方式,则是我们不得而知的。可能有些牛人或者神人能够想出一些高明的算法,在细节上做足功夫,使得第三种加锁方式效率上比第二种加锁更为适应整个系统。但是,就目前分析而言,无疑第二种加锁方式更具有优势,所以我们将以第二种加锁方式为基本原则,来系统的分析讲解数据库四种隔离级别的实质内容。

那么接下来,我们将正式进入主题,我将会以各种案例各种实验来讲述四种隔离级别究竟是怎么一回事。

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值