sql server 阻塞与锁

Posted 小文叔

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了sql server 阻塞与锁相关的知识,希望对你有一定的参考价值。

SQL Server阻塞与锁

在讨论阻塞与加锁之前,需要先理解一些核心概念:并发性、事务、隔离级别、阻塞锁及死锁。

并发性是指多个进程在相同时间访问或者更改共享数据的能力。一般情况而言,一个系统在互不干扰的情况下可以激活的并发用户的进程数越多,该系统的并发性就越强。就像通常所说的系统性能表现,系统同时处理的并发用户数越多,说明系统的性能越强。当正在更改数据的进程阻止其他进程读取该数据时,或者当读取数据的进程阻止其它进程更改该数据时,并发行会减弱。另外,当多个进程试图同时更改相同数据时,且无法在不牺牲数据的一致性的前提下都能成功时,并发性也会受到影响。在SQL Server中提供了两种方法对并发性进行有效的管理:悲观控制法和乐观控制法。悲观控制法认为不同进程将会试图同时读写相同的数据,并且通过锁来阻塞另一个进程对正在使用的数据进行访问,即当一个进程读数据时,需要通过加锁防止其它进程对它进行写操作,当一个进程对写数据时,需要通过加锁防止其它进程对它进行读操作,这样,读数据进程阻塞写数据进程,写数据进程阻塞读数据进程。乐观控制方法假设系统中很少数据在读进程和写进程中发生交叉的情况,因此不需要对数据进行加锁操作,这意味着读进程不会阻塞写进程,写进程不会阻塞读进程。

SQL Server默认设置是悲观式并发控制,因此为了提高应用程序的并发性,需要对资源加锁和解锁,在SQL Server 2005发布后,可以采用乐观式的策略,之所以可以采用这种策略,是因为SQL Server可以使用数据行版本控制,允许读进程读取的数据是写进程开始进行修改之前的版本。在进行并发容易产生以下几种情况的异常:丢失更新、脏读、不可重复读、幻影。

1) 丢失更新(Lost Updates)

丢失更新是指两个进程对同一个数据进行读取操作,并且同时试图对该数据值进行更新时,这很容易出现更新初始值错误的现象。例如:一个人找两个朋友借钱,这个人银行卡帐中的余额为5000块,结果这两个朋友同时向这张银行卡存款,假设朋友A向这张银行卡帐户中存5000块,朋友B向这张银行卡帐户中存6000块,当朋友A存款完成后,银行的出纳员将银行卡帐户中的钱修改为10000块(5000+5000),当朋友B存款完成后,银行的出纳员将银行卡帐户中的钱修改为11000块(5000+6000),由于出现更新丢失,导致损失了5000块,这个结果显然无法接受。

2) 脏读(Dirty Reads)

脏读是指对提交的数据进行读取操作,一个进程对数据进行了修改,但在提交修改后的数据时取消了当前的操作,即没有更新成功,但另一个进程已经读取了修改后的值并开始使用它。例如:你有一套房产,最近由于资金紧张,想将该处房产卖掉,这样房产中介公司的经纪人A则将该信息写入到公司资源池中(这样公司所有的房产销售人员都可以看该信息),但过了一些时间通过另外的方式筹到了这笔资金,你又不打算卖掉这处房产,所以通知经纪人A取消该房产信息,但此时经纪人B已经找到了买家,此时经纪人B读取到的就是脏数据。

3) 不可重复读(Non-Repeatable Reads)

不可重复读是指一个进程在同一个事务中对数据进行了两次读取操作,这样第二次读到的数据很可能与第一次读到的数据不一致。例如:超市统计当前8台收银机收银总和,将每台收银机的金额读取出来并相加得到总和,之后再次逐台收银机相加检查统计出来的总和,结果发现这两次计算出来的结果并不相同,因为在第一次统计结束后,有的收银机再次收银了,所以第二次计算出来的结果与第一次计算出来的结果不同。

4) 幻影(Phantoms)

幻影是指一个处理插入或删除一行数据的事务,在处理该事务时,该数据行在另一个事务可读取的范围内。例如:在中国人才热线上去搜索软件测试相关职位,假设可以搜索到20家公司,于是说准备好简历,向这20家公司投简历,然后再按E-mail地址进行搜索,但搜索发现有25个E-mail地址,那么第一次搜索中没有找到的职位就是幻影数据行。

事务是数据库并发控制的基本单元,可以执行一个或多个动作。事务分为显式事务和隐式事务,显式事务是显式的开始一个事务并显式的滚回或提交事务,除了显式的事务还有隐式的了,隐式事务是数据库自己根据情况完成的事务处理,如单独的select、update、delete、select语句。在关系数据管理系统(Relational Database Management System,RDBMS)中,事务必须满足ACID属性。

1) 原子性(Atomicity)

一个事务是一个整体所有的动作都发生并都被执行,要么全部执行成功,如果在执行过程中有一个动作失败,那么事务中之前所有的动作都必须回滚,就当之前的动作没有执行。例如:做一个入库操作,在这个事务里,审核入库单和修改库存作为一个整体,要不单据变成审核过同时库存增加相应的值,要不就是单据未审核同时库存不变。

2) 一致性(Consistency)

一致性是指事务不能违背数据库的完整性规则,当事务处理结束后,数据库必须处于一个一致的状态。例如:库存的值不能为负数,sex的字段必须是male或female。

3) 隔离性(Isolation)

SQL Server在设计时应该注意其必须有为很多并发用户供服务,但在从用户的角度来看,数据集必须看像去就像该用户是系统中唯一的用户,每个事务都必须是完全自包含的,并且所作的修改必须对于任何其它事务是不可读,SQL Server中关于事务的隔离有5种不同的级别,下面的内容中会详细介绍。

4) 持久性(Durability)

持久性是指事务提交后,必须持久的保存,即使事务处理完成后系统发生故障,如果在事务执行的过程中发生故障那么事务就会全部撤销,当SQL Server提交事务成功时,在提交成功的消息返回给用户之前,需要将相关的信息写入到事务日志中。

SQL Server中关于事务的隔离有5种不同的级别:读取未提交、读取提交、可重复读、可串行化和快照;

1) 读取未提交(Read unconnitted)

这是最低隔离,它可以允许脏读、不可重复读和幻影,如果一个事务已经开始写数据,则另外一个数据不允许同时进行写操作,只允许对该事务进行读操作,如果不介意脏读并且希望以可能的最轻量级接触来读取数据,则可以使用该隔离级别,在读取数据时,这种方式在数据上不加任何锁。

2) 读取提交(Read connitted)

这种隔离方式不允许脏读,但仍可能存在不可重复读和幻影读取数据的事务允许其它事务继续访问该行数据,但未提交的写事务则会禁止其它事务访问该行,这是SQL Server的默认隔离级别,通常可以在性能和业务需求之间提供最佳平衡。当读操作的语句执行完成后,所持有的锁都会被释放,即使是在事务内部也是如此。

3) 可重复读(Repeatable read)

该隔离可以防止脏读以及不可重复读,但是幻影仍然可能发生,读取数据的事务将会禁止写事务,但允许读事务,如果是写事务则禁止任何其它的事务,如果在事务持续期间保持读锁,以防止其它事务修改数据,那么实现可重复读也是有可能的。

4) 可串行化(Serializable)

该隔离方式要求事务序列化执行,对数据只能进行串行化访问,并且在事务持续一直保持着锁定状态,这样可以有效的锁定那此虽然不存在但位于键范围内的数据行,防止所有的副作用,该隔离程度是最高级别的,不允许高并发性执行。

5) 快照(Snapshot)

快照隔离级别是唯一可用的乐观的隔离级别,其使用的是数据行版本控制,而不是锁定,这意味着在一个事务中,由于读一致性可以通过行版本控制实现,因此同样的数据总是可以像在序列化级别上一样被读取而不必为防止来自其他事务的更改而被锁定。但是仍然允许更新冲突的发生,这在事务串行化运行中是不会发生的,这种冲突发生在一个快照事务内部即将改变的数据被另外一个事务并发更改时,在执行过程中SQL Server能够自动进行检测,如果发生冲突,则快照事务进行回滚,以防止丢失更新。

关于隔离级别与可能的异常关系见表。

 

如果希望更改默认事务隔离级别,可以使用命令:

set transaction isolation level 隔离级别名

go

例如:如果希望将隔离级别更改为repeatable read(可重复读),使用的命令如下:

set transaction isolation level repeatable read

go

无论那种模型,锁在并发控制中是必需的,SQL Server会自动处理锁,在SQL Server中有三种基本的粒度级别:行锁、页锁和表锁。

行锁(row-level lock):是指锁定一个数据页或索引页中的一行数据。行是可以锁定的最小空间,行级锁占用的数据资源最少,所以在事务的处理过程中,允许其它事务继续操纵同一个表或者同一个页的其它数据,大大降低了其它事务等待处理的时间,提高了系统的并发性。

页锁(page lock):用于锁定一个页,一个页由8KB的数据或索引信息组成。在事务的操纵过程中,无论事务处理数据的多少,每一次都锁定一页,在这个页上的数据不能被其它事务操纵,页级锁锁定的资源比行级锁锁定的数据资源多,在页级锁中,即使是一个事务只操纵页上的一行数据,那么该页上的其它数据行也不能被其它事务使用。

表锁(table lock):用于锁定包含数据与索引的整个表。用于表级别的锁定。事务在操纵某一个表的数据时,锁定了这个数据所在的整个表,其它事务不能访问该表中的其它数据,当事务处理的数据量比较大时,一般使用表级锁,表级锁的特点是使用比较少的系统资源,但是却占用比较多的数据资源。

在执行过程中获得每种锁均占用一定数量的系统资源,但并不是使用粒度越小的锁越好,在某个临界点上,使用一个较大粒度的锁会更使用多个小粒度的锁更有效率,当达到该临界点时,SQL Server将锁升级为表锁,这个过程被称为锁升级。当事务中某个语句在一个对象上使用的锁的数量超过5000时,SQL Server将试着升级到表锁,SQL Server还会在出现内存压力时试图升级锁,升级时都会升级到表锁,一般情况下,查询优化器会在创建执行计划时选择最合适的锁粒度,因此不会经常发生锁升级。

SQL Server支持的锁定模式包括:共享锁、排他锁、更新锁、架构锁和意向锁。

共享锁(S):用于保护资源,只允许对其进行只读数据操作,当资源上存在共享(S)锁时,其它事务均不能修改数据。共享锁是非独占的,即允许多个并发事务读取该锁定的资源,默认情况下,数据被读取后,SQL Server立即释放共享锁。例如,执行查询“SELECT * FROM TEST”时,首先锁定第一页,读取之后,释放对第一页的锁定,然后锁定第二页。这样,就允许在读操作过程中,修改未被锁定的第一页。但是,事务隔离级别连接选项设置和SELECT语句中的锁定设置都可以改变SQL Server的这种默认设置。例如,“SELECT * FROM TEST HOLDLOCK”就要求在整个查询过程中,保持对表的锁定,直到查询完成才释放锁定。

排他锁(X):是数据修改时需要申请的锁,例如插入、更新或删除,确保不能同时对同一资源进行多个更新,只能对数据加上一个排他锁。如果事务T对数据A加上排他锁后,则其它事务不能再对A加任何类型的锁,获准排他锁的事务既能读数据,又能修改数据。

其它的锁模式要么是以上两者的混合,要么用来对上述的两种锁模式提供坚持,主要包括:更新锁、架构锁和意向锁。

更新锁(U):防止常见形式的死锁,当一个进程需要在更新之前读取数据时需要申请更新锁,每次只有一个事务可以获得资源上的更新锁,如果事务修改资源,则更新锁将转换为排他锁。更新锁不会阻塞共享锁。

架构锁:在执行依赖于表架构的操作时使用。架构锁的类型是架构修改 (Sch-M) 和架构稳定性 (Sch-S)。执行表的数据定义语言操作时(如添加列或除去表)时,使用架构修改(Sch-M)锁,当编译查询时,使用架构稳定性(Sch-S)锁,架构稳定性(Sch-S)锁不阻塞任何事务锁,包括排它锁。因此在编译查询时,其它事务(包括在表上有排它锁的事务)都能继续运行,但不能在表上执行数据定义语言操作。

意向锁:建立锁层次结构,这些锁指示事务正在处理层次结构中较低级别的某些资源,而不是所有资源,较低级别的资源将具有共享锁、更新锁或排他锁。意向锁说明SQL Server有在资源的低层获得共享锁或排它锁的意向。例如,表级的共享意向锁说明事务意图将排它锁释放到表中的页或者行。意向锁又可以分为共享意向锁、独占意向锁和共享式独占意向锁。共享意向锁说明事务意图在共享意向锁所锁定的低层资源上放置共享锁来读取数据,独占意向锁说明事务意图在共享意向锁所锁定的低层资源上放置排它锁来修改数据。共享式排它锁说明事务允许其他事务使用共享锁来读取顶层资源,并意图在该资源低层上放置排它锁。

通常有两种情况会导致加锁问题:阻塞锁和死锁。阻塞锁是当一个进程对资源进行了加锁,但该资源已经被另一个进程锁定,这就产生了锁冲突,即出现了阻塞,当然SQL Server在处理并发时本身就是采用该方式对资源地行处理,但是如果第一个进程需要等待很长时间才能获得锁的话,那么显然就产生了性能问题,在工作中还可能产生一条阻塞进程链,当统计中发现阻塞锁非常高的时候,那么阻塞锁就成了影响性能的问题了。那么进程在等待多久获得锁资源才是合理的呢?SQL Server有一个锁超时的原理,默认情况下SQL Server锁定超时的时间为两秒钟,除了尝试访问数据并有可能发生超时以外,在对资源进行锁定之前,没有办法测试该资源是否已被锁定。LOCK_TIMEOUT设置允许应用程序设置语句等待被阻塞资源的最长等待时间,当语句等待的时间超过LOCK_TIMEOUT设置的时间时,将自动取消阻塞的语句,并将错误消息 SSCE_M_LOCKTIMEOUT返回给应用程序。如果应用程序不捕获错误,它可以继续执行,但将不会知道事务中的单个语句已被取消。由于事务中后面的语句可能依赖于未执行的语句,因此可能会出现错误。若要设置会话的当前LOCK_TIMEOUT设置,请执行SET LOCK_TIMEOUT语法,如下列代码示例所示:

SET LOCK_TIMEOUT 2000

死锁是指当两个进程各自拥有一个锁,而这个锁是对方继续运行所需要的,这样就会出现两个进程相互阻止对方运行的情况,这就会出现死锁,如果对这种情况不进行处理的话,那么就将无限期的等待下去,在SQL Server中内置了死锁探测,每5秒钟锁监视器就会检查死锁状态,如果发现死锁,这个检查就会变的很频繁,SQL Server通常选择终止回滚成本较低的进程来解决死锁问题。如果是在SQL Server Mobile中则使用锁定超时处理死锁,因为SQL Server Mobile中没有死锁探测器,如果一个事务试图锁定某个资源但无法在锁定超时时间内实现锁定,就会出现错误。锁定超时可确保一个事务不会无限期地等待由另一个事务控制的资源,可以使用LOCK_TIMEOUT修改锁定超时。如果一个死锁涉及到两个事务,则其中一个事务会等待资源超时,同时会出现错误。超时的事务仍然处于活动状态;它不会被提交或回滚。另一个事务于是获得了所需的锁,然后继续执行

 

以上是关于sql server 阻塞与锁的主要内容,如果未能解决你的问题,请参考以下文章

Sql Server 优化----SQL语句的执行方式与锁以及阻塞的关系

SQL Server 事务与锁

SqlServer系列浅谈SQL Server事务与锁(上篇)

SQL Server 事务与锁

SQL Server 事务与锁

SQL Server中的事务与锁