关于SQL Server的锁的问题

提示:这里所摘抄的关于的知识囿的是不同sql server版本的对应于特定版本时会有问题。


(一). 为什么要引入

当多个用户同时对数据库的并发操作时会带来以下数据不一致的问题:

A,B两個用户读同一数据并进行修改,其中一个用户的修改结果破坏了另一个修改的结果,比如订票系统

A用户修改了数据,随后B用户又读出该数据,但A用戶因为某些原因取消了对数据的修改,数据恢复原值,此时B得到的数据就与数据库内的数据产生了不一致

A用户读取数据,随后B用户读出该数据并修改,此时A用户再读取数据时发现前后两次的值不一致

并发控制的主要方法是封,就是在一段时间内禁止用户做某些操作以避免产生数据不一致

◆的类别有两种分法:

}

最近在使用综合各方面原因,選择了SQL Server2008.为了对使用的工具有更加深入的了解所以仔细地了经典翻译书籍《SQL Server 2008高级程序设计》,在此将过程中的感想和理解记录下来以备鉯后查询温习,也希望对其他使用SQL Sercer的小伙伴们有启发帮助

对数据库而言,并发是最主要的问题它用于表示两个或多个用户同时对同一個对象进行交互的情况。用户越多——更特定的说事物越多,即在同一时刻可以合理成功完成的事情越多并发性也就越高。

在联机事務处理(Online Transaction Processing, OLTP)环境下并发通常是在数据中首先要处理的事情。对于系统的性能来说对并发问题的处理是至关重要的。在数据库中对并发进荇处理的基本方法,是一个称作定(locking)的过程

是一种机制,用于防止一个过程在对象上进行操作时同某些已经在该对象上完成的事情发生沖突。也就是说如果某人已经先在对象上进行操作,你就不能再对该对象进行操作在对象能操作还是不能操作,依赖于其他的用户正茬做什么

通过这种操作机制,SQL Server能够帮助我们避免那些因为并发问题而生成的多种不同错误我们会对并发错误发生的可能性进行检查,並了解为了防止这些错误应该如何设置事务隔离等级(isolation level)。

总的来说防止并发问题主要有两种途径,分别是使用和设置隔离级别使用来處理并发问题更加精细,需要对每一个操作对象进行的设置这期间难免会带来争用的问题以及其他性能问题;设置隔离级别的方法是对用戶的一次连接进行的整体设置,粒度较粗但是可以避免有时会带来的争用问题设计者可以根据设计需要自行选择这两种方式。

一、使用解决并发问题

归纳来说主要解决4类主要问题:脏读(dirty read)、不可重复读(No-Repeatable Read)、幻影(phantom)、丢失更新

何为脏读呢当事务读取一条记录,洏该记录是另一事务尚未完成的一部分时就会发生脏读。如果每一个事物都是正常完成了那么这看上去像是不会存在问题。但是如果该事务被回滚了呢?!看下面这个例子:

事务2现在正在使用一个已经无效了的值!如果想要试图返回去审核想看看该数字是从哪里来嘚,会因为发现根本无法追踪这是非常令人头疼的事。

针对这种脏读的情况如果使用SQL Server默认的事务隔离级别(称作READ COMMITTED),就不会发生这种事叻。

类似于脏读当在一个事务中两次读取记录,并且在两次读取之间另一个单独的事务修改了该数据,这是会导致不可重复读看下媔这个例子:

(完成,等待清除然后继续) -25(如果没有check约束强制大于0)或错误547(如果有CHECK约束)

在此例子中,约束要求value值必须是大于0的值

可鉯看到,在实际的运行过程中最终结果是-25,显然又出错了!

事务1已经预先扫描(在某些情况下这是很好的行为)以确认值是有效的而且倳务可以继续下去。问题在于由于进行了UPDATE操作,事务2让事务1做出另外的决定性举动如果表上没有任何CHECK约束可以避免负数值,那么实际仩这个值会被设置为-25——尽管逻辑上看来这里已经通过IF语句防止此事的发生

只有两种方法可以防止出现这个问题:

--创建CHECK约束并监视547号错誤;

但是,这里不得不提醒一句其实将隔离级别设置为REPEATABLE READ或SERIALIZABLE带来的麻烦会很多。所以使用要慎重比较而言,还是设置CHECK约束作用更明显

什么是幻影呢?我们通过一个例子来说明

假设你正在开一个快餐厅,有很多雇员拿着政府规定的“最低工资”政府刚刚决定将最低工資标准从每小时6.55美元提高到每小时7.25美元,你希望通过更新名为Employees的表将那些收入少于每小时7.25美元的工资水平提高到最低工资水平。你会执荇一个非常简单的语句:

这是小事一桩对吗?错了!为了说明这个问题这里你肯定会得到一条错误消息:

当你快速运行一条SELECT语句,检查少于7.25美元的值肯定会找到一条记录。问题是“你怎么会在这里呢!我刚刚才更新了应该修改的内容呀!”你的确运行了这条语句而其它运行的很好——不过这里只是遇到了幻影。

这种幻影情况是比较罕见的只有在特定的环境下才会发生。简言之正在运行UPDATE更新的时候,如果另外有人恰好在此时执行了INSERT语句就会出现这种情况。因为它是一个完整的新行它上面没有,所以INSERT语句也执行得很好

解决这個问题的唯一的办法就是将事务的隔离级别设置为SERIALIZABLE,此时对表的所有更新都不能在WHERE子句内部,否则它们就会被定

当成功将一个更新写入数據库中,但是又被另一个事务意外的覆盖了就会发生丢失更新的现象。什么意思呢看下面的这个例子:

假设你是公司的信用分析师。伱接到一个电话说你的客户X达到了他的信用额度,并希望提高它因此你要提取这个客户的信息以查看详情。你看到客户的信用额度是5000媄元而且似乎总能按时还款。

在你浏览的时候你所在的信用部门的另一个人Sally,正在提取客户X的记录,并更新了地址在他提取用户记录嘚时候,记录中的信用额度是5000美元

此时,你决定将客户X的信用额度提升到7500美元并按下了回车键。数据库现在会显示对于客户X其信用額度已经是7500美元了。

现在Sally完成了自己对地址信息的更改但是他使用的是跟你相同的编辑屏幕——也就是说他更新的是整条记录。还记得茬他的屏幕上信用额度显示的是多少吗5000美元。数据库再次把客户X的信用额度设置为5000美元你的更新丢失了!

要解决这个问题的方案取决於自己的代码,在读取数据和对数据进行更新的这两个时间间隔之内如何识别另一个链接对记录的更新操作。使用不同的访问方式识別机制也会发生很多变化。

在上面这个问题中实际上就是两个人都提取了一条完整的记录,你改了前部分我改了后部分。然后发生了互相覆盖怎么解决这个问题呢?其实很简单每个人在更新数据时,只改变自己改动的字段没改动的就不要更新了就可以了。那么這里就要涉及到一个可以定的资源的问题了。

在SQL Server中有6种资源是可的,它们形成分级层次的级别越高,其粒度就越小(也就是说如果茬某些层叠的操作中,选择的级别越高被定的对象数量也就越多,因为包含它们的对象已经被定)这里按照粒度的升序列出了它们:

整个数据库都被定。这通常只在更改数据库模式的时候才会发生

:整个表被定。它包含了同该表相关联的所有数据对象包括实际的数據行(其中的每项内容)以及同表相关联的所有索引上的所有键。

:定整个区段记住一个区段由8页组成,所以区段定意味着控制了整个區段在该区段上的8个数据或索引页,以及在这8页上的所有数据行

:定该页上的所有数据或索引键。

:这是在索引上的个别键或一系列鍵上的在同一个索引页上的其他键不受影响。

:尽管从技术上说是放置在行的标识符(一个内部的SQL Server构件)上但是实际上它可以定整个荇。

基于更改数据内容的准确性考虑当然是定的内容越精细越好。比如说操作时定一个字段肯定比定一个表要好得多因为定一个字段並不会影响其他用户在同一时刻对其他字段的操作;但是如果定一个表,那么在这个用户对表内容进行操作的时候其他用户就无法对表嘚任何内容进行操作了。但是设置的过于精细也会带来性能上的负面影响。这就要考虑升级的问题了!

升级是指认识到当定的项目数量较少时,维持一个较精细的粒度级别(比如用行代替页)更加合理不过,越来越多的项目被定维护这些所带来的开销会对性能产生影响。它可能会导致在一个地方存留的时间过长并因此带来争用的问题。在某个位置上停留的时间越长有人希望得到这个特定记录的鈳能性越大。

当要维护的的数量达到某个阈值的时候被升级到下一个更高的级别,因为不用再对较低级别的进行紧密的管理(释放资源並提高速度但不考虑争用)。

注意升级是基于的数量,而不是用户的数量重要性在于可以通过执行大规模更新来单独定一个表。行萣可以升级为页定然后再升级为表定。这意味着可以将其他所有的用户定在表之外如果查询使用了多个表,就可以将每个用户都定在這些表之外

的应用在解决了一些并行问题的同时,也带来了用户对于对象的争用问题这也是不得不考虑的问题,因为没有用户想要在查询时还得不定时的等待要想解决这个问题,就需要设计者合理的选择使用的的模式什么是模式呢?一句话概括就是有些模式的是楿互排斥的,我工作时你就只能等着我释放了你才能工作(这就会出现争用问题了);而有些模式是可以同时工作的,也就是兼容的那么大家就可以同时工作而互不影响了。为何达到理想的系统运行效果设计者需要根据查询要求合理的设计选择每个任务的模式。

这是朂基本的类型只有读取数据的时候才会用到共享——也就是说不需要修改任何东西。共享与其他的共享之间是兼容的这并不意味着它鈈会给你带来麻烦——虽然共享不介意其他类型的,但还是有一些其他的不喜欢共享

共享会告诉其他的你现在在这里。它们并没有什么目的性不过却不容忽略。只是拥有共享的事物可以防止用户在其上发生脏读之类的操作。

排他名副其实它与其他类型的都不兼容。洳果已经存在其他类型的就无法使用排他。当排他活动的时候在资源上也不能创建任何类型的新。这样可以防止两个人在同一时刻进荇更新、删除或进行其他相关的操作

更新是共享和排他之间的某种混合体。更新是一种特殊类型的占位符请想象一下——为了进行UPDATE,需要验证WHERE子句(假设有一个WHERE子句)以推算需要更新的行。这意味着在真正需要物理更新之前只需要一个共享在进行物理更新的时候,財需要排他

更新意味着在完成对数据的初始扫描,从而确认需要更新的准确内容之后共享变成排他。这表明在更新过程中要经历两个鈈同的阶段:

首先第一个阶段是确认满足WHERE子句中的条件的内容(了解要更新的内容)。这是拥有更新的更新查询的一部分其次,在这個阶段如果确实决定执行更新,就将换成排他否则,转换为共享

设计更新的目的和好处是形成一道屏障,以防止一种死变体的发生死本身并不是一种类型,而是一种已经形成的矛盾的状态如果因为另一个持有了相应的资源,一个就不能完成自己要做的事情从而释放就会形成死。问题是相对的资源在等待第一个事务的释放。

以如果没有更新为例那么就会经常出现死。在共享方式下运行两个更噺查询查询A完成了自己的查询,并等待进行物理更新他希望升级为排他,但是不可以因为查询B正在完成自己的查询。查询B完成了自巳的查询后需要进行物理更新。为此查询B希望将自己升级为排他,但是不可以因为查询A还在等待。这就陷入了僵局

与此相反,更噺从创建伊始就禁止生成其他的更新第二个事务试图获得更新的时候,这个新的事务会被置为等待状态等待超时;在这段时间里,是鈈会被允许的如果在该超时期限到期之前第一个释放了,那么该就会被授予新的请求者随后可以继续这个过程。如果没有就会产生┅个错误。

更新只和共享和意向共享兼容

意向是一个真正的占位符,用户处理对象层次问题想象一下这样一种情况,假设在行上建立叻一个但是另一些人要在页上、或者在区上建立,或者对一个表进行修改你肯定不希望有另外的事务以更高的级别在自己的事务的周圍运行,是吧

如果没有意向,这个更高级别的对象甚至不知道你已经在一个较低的级别上持有意向可以提高性能,因为SQL Server不需要检测表Φ每行或页上的只需要在表级别上检测意向就可以确认一个事务是否可以安全的住整张表。意向有三种不同的形式:

意向共享:已经或鍺将要在一个较低级别的位置上建立共享例如在页上可以创建页级共享。这种类型的只应用在表和页上意向排他:它与意向共享类似,只是在较低级别的项目上要放置的排他意向排他共享:共享已经或将要位于对象层次结构的较底层,但是意向是要修改数据所以它茬某个时刻会变成意向排他。 (5)模式

模式修改(Sch-M):对模式的改变被应用在对象上在Sch-M定期间,不能针对对象运行任何查询或其他CREATE、ALTER或DROP语呴模式稳定(Sch-S):它非常类似于共享。这种的唯一目的是为了防止在对象上为其他查询(或CREATE、ALTER和DROP语句)而生成的在活动时出现Sch-M。它与其怹所有类型的都兼容 (6)批量更新

批量更新(BU)只是表的一种变种,其中只有一点(但是很重要)不同批量更新允许并行载入数据——也就是说,表被定以防止其他任何“正常”(T-SQL语句)的活动不过可以同时执行多个BULK INSERT或bcp操作。

到这里为止我们已经详细的介绍了的作鼡以及各种的特点,大家如果已经将各种的功能牢记于心了那么剩下的问题就是具体该怎么使用它们呢?接下来我们就介绍怎么指定特定的类型!

有时候可能希望在查询中或在整个事务中更好地控制行为。这可以通过使用所谓的优化器提示来实现 优化器提示是一种明確的告诉SQL Server将升级到指定级别的方法。它们位于要操作的表的名称的后面(在SQL语句中)并按照下边的方式被指定: 我们先来看一下具体使鼡的语法方式:使用它们的语法相当简单——只需要在表名后面加上它,或是在所使用的别名后加上它: .... FROM

上面黑体标出的部分就是指定的嘚类型这里TABLOCKX表示排他表。还有很多其他类型的将在下面这个表中一一列出,用法跟上面例子相同
一旦通过语句在事务中建立了,在倳务结束(通过COMMIT或ROLLBACK)之后才会释放这个在执行插入操作时,如果要插入的记录同建立该的查询中的WHERE子句中的条件相匹配(不是幻影)那么插入操作也被阻止。这是最高的隔离级别并且绝对保证数据的一致性。
不获取(甚至不使用共享)也不支持其他的这是一个非常赽速的选项,可能产生脏读也可能会产生其他问题
这是默认选项。支持所有的但是处理获得的的方式取决于数据库的 READ_COMMITTED_SNAPSHOT选项。如果打开這个选项那么READCOMMITTED 不会获取,作为替代方案他会通过使用一个行版本模式来确认是否发生了冲突。在实际情况下这样做的效果不错,只囿当需要提供向后兼容性而且需要较好的性能时,才应该使用READCOMMITTED方法
一旦在事务中通过语句建立了,直到事务结束才会释放这个(通过ROLLBACK戓COMMIT). 不过可以插入新的数据
不是等待被释放,而是跳过所有被定的行这种跳过的行为仅限于行(仍然会等待页、区段和表上的),并苴只能用在SELECT语句上
立刻导致查询失败而不是等待(如果检测到任何)
即使优化器选择其他较小的粒度定策略,它也会强行将的初始级别設置为行级如果的数量达到系统的阈值,它不会阻止升级到更小的粒度级别
使用页级不理会优化器所做出的其他选择。在两种方式中怹比较有用有时候你知道对于资源保存来说,页级比行更加合适另一些时候,优化器会选择表而你希望将争用最小化
强制使用完整嘚表,不理会管理器使用了什么事实上这样可以加快对已知表的扫描,但是如果其他用户希望修改表中的数据也会导致比较大的争用問题。
同TABLOCK相似不过要创建排他——根据TRANSACTION ISOLATION LEVEL的设置方式,在语句或事务期间将其他所有的用户定在表外
使用更新来替代共享。在同死的斗爭中并没有充分发挥这个工具的功能。因为它仍然允许其他用户使用共享不过会确保不会进行数据修改(其他更新),直至语句或事務终止(大概在继续运行并更新行之后)
虽然植根于TABLOCKX,这是它第一次出现在SQL Server2000中这样做的好处是你不用理会自己已经选中(或没选中)的粒喥,可以指定一个排他

二、设置隔离级别解决并发问题

在上面的介绍中我们已经看到,通过使用不同的所策略可以防止发生几种不同嘚问题。我们还看到了有哪些类型的可供使用以及这些影响资源的可用性的方式。现在是时候深入了解一下进程管理块是如何协同工作鉯确保全面的数据完整性并且确保你能够得到所期望的结果。

关于事务和之间的关系是盘根错节密不可分的在默认情况下,一旦创建叻任何同数据修改相关的这个将在整个事务的持续期间内被持有。如果持续的时间很长就意味着这个会在这段很长的时间内一直防止其他进程访问对象。这也是一个问题

这只是在默认情况。事实上可以设置5种不同类型的隔离级别:

在它们之间切换的语法是很直观的:

改变隔离级别,只会对当前的链接产生影响因此不用担心他会对其他用户产生不良的影响。

(关于每一个级别的详细介绍此处就不詳细介绍了,需要了解的朋友可以查阅《SQL Server 2008高级程序设计》)

}

可以通过查询 DMV知道数据库里有没囿表被住了或者阻塞了。

如果时间过长说明是阻塞了

查询到 Block别人的Session 你可以手动 Kill它那么别的进程就可以正常执行了

}

我要回帖

更多关于 忘带钥匙开锁小窍门 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信