1、 并发用户少的时候,一切正常,但是随着用户数量增多,性能越来越慢。
2、 客户端经常收到以下错误:
Error 1222:Lock request time out period exceeded.(已超过锁请求超时时段)
Error 1205:Your transaction(process ID #XX) was deadlocked on{lock|communication buffer|thread} resources with another process and has beenchosen as the deadlock victim.Rerun your transaction.(事务(进程ID XX)与另一个进程被死锁在XX资源上,并且已被选作死锁牺牲品。请重新运行该事务。)
超时错误:Timeout expired.The timeout period elapsed prior to completion ofthe operation or the server is not responding.
3、 应用程序运行很慢,但是SQL Server 硬盘、CPU利用率很低。运行sp_who命令很快返回。
4、 有些查询能经行,但是有些特定查询或者修改总是不能返回。
5、 重启SQLServer就能解决。但是有可能跑一段时间以后又出现问题。
堵塞和死锁是性能问题,更是设计问题。而不是数据库服务器自身的问题。从根本上解决死锁问题必须从设计本身着手。
在了解死锁和堵塞原因之前,先要了解关系数据库中的一个概念:事务
堵塞和死锁的3大因素:连续持有锁时间过长,数目过多,粒度过大产生的原因。
1、 事务是单个逻辑工作单元执行的一系列操作。必须具有:原子性、一致性、隔离性和持久性(ACID):
l 一致性:事务完成时,数据必须保持一致状态。事务结束时,所有内部数据结构必须都是正确的。
l 隔离性:与其他并发事务所做的修改必须隔离。虽然用户在并发操作,但是事务是串行执行的。
l 持久性:事务完成后,影响是永久的。
l 原子性:事务要么做、要么不做。
2、 从业务逻辑上实现ACID,有两方面:
2.1、程序员要负责启动和结束事务,确定一个事务的范围。并要控制好合适的结束时间。
2.2、SQL Server数据库引擎强制该事务的物理完整性:
2.2.1、锁定资源,使事务保持隔离。
2.2.2、先写入日志方式,保证事务的持久性。
2.2.3、事务管理特性,强制保证事务的原子性和一致性。如果事务完成不了,数据库引擎会撤销所做的操作。
所以,锁是SQLServer实现事务隔离的一部分,阻塞也是事务隔离的体现。是用户使用事务所要付出的代价。开发者和DBA的工作不是去消除阻塞,而是把阻塞时间和范围控制在合理的范围内。完全消除阻塞是不可能的。
为了减少大范围阻塞,建议做到以下几点:
1、 申请资源的互斥度:如果不同的连接申请的锁都是互相兼容的,那么不会产生阻塞。
2、 锁的范围和数目多少:良好的设计可以使申请的锁的粒度和数目控制在最小范围内。
3、 事务持有锁资源的时间长短:对共同所需的锁,持有时间越短越好。
数据库引擎可以锁定的资源:
资 源 |
说 明 |
RID |
用于锁定堆(Heap)中的某一行,没有聚集索引的表为“堆”。 |
Key |
用于锁定索引上的某一行,或者某个索引键。 |
Page |
锁定数据库的一个8K页,例如数据页或者索引页。 |
Extent |
一组连续的8页 |
HoBT |
锁定整个堆或者B树的锁。 |
Table |
锁定包括所有数据和索引的整个表。 |
File |
数据库文件。 |
Application |
应用程序专用的资源。 |
MetaData |
元数据锁。 |
Application_Unit |
分配单元。 |
Database |
整个数据库。 |
数据库引擎使用的资源锁模式:
锁模式 |
说 明 |
共享(S) |
用于不更改或者不更新数据的读取操作,如SELECT 语句 |
更新(U) |
用于可更新的资源中,防止多会话读取、锁定及随后可能更新的资源更新时发生常见形式的死锁。 |
排他(X) |
用于数据修改,确保不会同时对同一资源进行多重更新。 |
意向 |
用于建立锁的层次结构,包含:意向共享(IS),意向排他(IX),意向排他共享(SIX) |
架构 |
执行依赖于表架构的操作时使用。架构修改(Sch-M)和架构稳定性(Sch-S) |
大容量更新(BU) |
在向表进行大容量数据复制且指定了TABLOCK提示时使用。 |
键范围 |
当使用可序列化事务隔离级别时保护查询读取的行的范围。确保再次运行查询时其他事务无法插入符合可序列化事务的查询的行。 |
下面是对表格的说明:
允许并发事务在封闭式并发控制下读取资源。此时其他事务不能修改数据。
在可重复读或可序列化事务中(避免幻读),一个修改操作需要先【读取数据即添加共享锁】,然后【修改数据即从共享锁转换成排他锁(X锁)】。如果此时有另外一个事务做同样的操作,就有可能形成死锁。为避免这个情况,SQL Server使用了更新锁(U锁)。一次只有一个事务可以获得更新锁。当事务真正需要修改数据时,就会把U锁转成X锁。
防止并发事务对资源进行访问。此时无法读写资源,除非使用NOLOCK提示或者未提交读隔离级别时才可以进行读操作。修改语句(insert/update/delete)合并了读、改操作。因为首先要读出来才能改。所以通常修改数据需要申请共享锁和排它锁。
数据库引擎使用意向锁来保护锁层次结构的底层资源。添加了意向锁后,可以防止其他排它锁对该资源的控制。意向锁可以提高性能,因为数据库引擎仅在表级检查意向锁。而不需要检查表中的每行每页
在表的DDL操作是会使用架构修改锁(Sch-M),防止其他用户对这个表的访问。在编译和执行查询时会使用架构稳定性锁(Sch-S),此锁不会阻止其他事务访问数据,但是会阻止DDL和DML操作。
对大容量复制数据时使用大容量更新锁(BU),并指定TABLOCK提示或使用sp_tableoption设置table lock on bulk load表选项。允许多线程将数据并发地加载到同一个表。防止其他不惊醒大容量加载数据的进程访问该表。
在使用可序列化(serializable)事务隔离级别时,对T-SQL读取的记录集,键范围锁可以隐式保护该记录集中包含的行范围。可以防止幻读。
如果请求锁的模式和现有模式不兼容,则请求新锁就会被迫进入等待状态。阻塞也就随之而来。
|
现有授予模式 |
|||||
请求模式 |
IS |
S |
U |
IX |
SIX |
X |
意向共享(IS) |
Y |
Y |
Y |
Y |
Y |
N |
共享(S) |
Y |
Y |
Y |
N |
N |
N |
更新(U) |
Y |
Y |
N |
N |
N |
N |
意向排他(IX) |
Y |
N |
N |
Y |
N |
N |
意向排他共享(SIX) |
Y |
N |
N |
N |
N |
N |
排他(X) |
N |
N |
N |
N |
N |
N |
申请锁的粒度越小,产生阻塞的几率就越小。
锁的释放速度越快,产生阻塞的几率越低。
影响锁的粒度:
1、 一个事务内部要访问或修改的数据量越大,索要申请的锁的数目就会越多,粒度也就可能越大。
2、 一个事务做的事情越复杂,它要申请的锁的范围就会越大。
3、 一个事务延续时间越长,它持有的锁的时间也会越长。
总结:事务的隔离级别影响锁的申请及释放的时间;而语句的执行计划,也会影响到锁的粒度和申请数量。
没有并发控制会出现以下情况:
l 丢失更新
l 未提交的依赖关系(脏读)
l 不一致的分析(不可重复读)
l 幻读
SQL Server数据库引擎支持的隔离级别:
l 未提交读(隔离事务的最低级别,只能保证不读物理上损坏的数据)
l 已提交读(默认级别,可防止脏读)
l 可重复读。
l 可序列化(最高级别,可防止幻影,事务之间完全隔离)
不同隔离级别允许的并发副作用:
隔离级别 |
脏读 |
不可重复读 |
幻读 |
未提交读 |
是 |
是 |
是 |
已提交读 |
否 |
是 |
是 |
可重复读 |
否 |
否 |
是 |
可序列化 |
否 |
否 |
否 |
可以读取由其他事务修改但未提交的数据,允许脏读。不会被其他锁阻塞。该选项和使用nolock设置相同。是隔离级别中限制最少的级别。
指定语句不能读取由其他事务修改但未提交的数据。避免脏读。但会产生不可重复读和幻象数据。是默认设置。此时会使用共享锁防止其他事务修改数据。语句运行完就会释放共享锁,而不是等到事务提交才释放。
指定语句不能读取由其他事务修改但未提交的数据,并且其他事务不能在当前事务完成之前修改由该事务读取的数据。此级别对事务中每个语句所涉及的所有数据设置了共享锁,并且一直持续到事务完毕。可以防止其他事务修改当前事务读取的任何行。
其他事务可以插入与当前事务语句中匹配的新行,如果当前事务重新执行该语句,会检索到新行,从而产生幻读。
由于并发性比默认设置低,所以只有在必要的时候才使用。
要求:
1、 语句不能读取已由其他事务修改但未提交的数据。
2、 任何其他事务都不能在当前事务完成前修改当前事务读取的数据。
3、 在当前事务完成前,其他事务不能使用当前事务中任何语句读取的键值插入新行。
通过加范围锁的方式来实现可序列化。范围锁会锁住当前事务的语句中搜索条件相匹配的键值范围,使得其他事务不可更新或插入任何行。直到事务结束。并发性最低。只有必要时才使用。该选项与select语句中的HOLDLOCK提示相同。
隔离级别 |
是否申请共享锁 |
何时释放 |
有无范围锁 |
未提交读 |
否 |
∕ |
无 |
已提交读 |
是 |
当前语句完成时 |
无 |
可重复读 |
是 |
事务提交时 |
无 |
可序列化 |
是 |
事务提交时 |
有 |
隔离的级别越高,共享锁持有时间越长,粒度越大。
如果阻塞发生在【共享锁】上,可以通过降低事务隔离级别得到缓解。
对于排它锁,所以隔离级别都是一样,要等到【事务结束】后才释放。此时不能通过降低事务隔离级别来实现。