一个查询的持续时间和事务上下文将决定其锁定时限,进而决定了它们对其他查询的影响。如果查询不在事务内执行(并且没有使用任何锁提示),那么对于 SELECT 语句而言,仅在实际读取某一资源时才会锁定该资源,但查询期间并不锁定该资源。而对于 INSERT、UPDATE 和 DELETE 语句,会在查询期间进行锁定,其目的在于实现数据一致性并允许在必要时进行回滚查询。
如果查询是在事务内执行的,那么锁的保留时间将由以下因素决定:查询类型、事务隔离级别、查询中是否使用了锁提示。有关锁定说明、锁提示和事务隔离级别的信息,请参阅《SQL Server 7.0 联机丛书》中的以下主题:
• | “了解 SQL Server 中的锁定功能” |
• | “锁定体系结构” |
• | “锁兼容性” |
• | “锁定提示” |
• | “更改 Oracle 和 SQL Server 中的默认锁定行为” |
• | 某个 SPID 将一组资源锁定了较长一段时间后,才释放这些资源。此类阻塞会随着时间推移而自行消失,但会导致系统性能降低。 |
• | SPID 锁定了一组资源,并且不再释放这些资源。此类阻塞不会自行消失,它会无限期地妨碍对受影响资源的访问。 |
收集阻塞信息
为了降低阻塞问题的解决难度,数据库管理员可以使用持续监视 SQL Server 上锁定和阻塞状态的 SQL 脚本。这些脚本可以提供特定实例在一段时间的快照,让用户对该问题有一个全面的了解。有关如何用 SQL 脚本监视阻塞的说明,请参阅以下 Microsoft 知识库文章:251004 (http://support.microsoft.com/kb/251004/) INF:如何监视 SQL Server 7.0 阻塞
271509 (http://support.microsoft.com/kb/271509/) INF:如何监视 SQL Server 2000 阻塞
本文中的脚本可执行以下任务。文中还给出了通过企业管理器或特定 SQL 查询获取该信息的方法(只要可能)。
1. | 识别位于阻塞链头的 SPID。 除了使用上述文章中的脚本外,还可以使用 SQL 企业管理器识别阻塞链头,方法如下:
|
||||||||
2. | 查找发生阻塞的 SPID 正在运行的查询。 脚本方法使用以下查询确定特定 SPID 发出的命令:
也可以使用 SQL 企业管理器,方法如下:
|
||||||||
3. | 查找发生阻塞的 SPID 正使用的锁的类型。 可以通过执行 sp_lock 系统存储过程,来确定该信息。也可以使用企业管理器,方法如下:
|
||||||||
4. | 查找发生阻塞的 SPID 的事务嵌套层和进程状态。 @@TRANCOUNT 全局变量中提供有 SPID 的事务嵌套层。但是,也可以通过查询 sysprocesses 表,从 SPID 外部确定该信息,方法如下:
返回的值是该 SPID 的 @@TRANCOUNT 值。它显示了发生阻塞的 SPID 的事务嵌套层,该值反过来解释了为什么该 SPID 会保留锁。例如,如果该值大于零,则该 SPID 处于事务的中间位置(这种情况下,它应当保留已获取的某些锁,具体情况取决于事务隔离级别)。使用 DBCC OPENTRAN database_name,还可以查看数据库中是否存在长期处于打开状态的事务。 |
收集 SQL Server 事件探查器跟踪信息
要彻底研究 SQL Server 上的阻塞问题,除了以上信息外,通常还需要捕获服务器上各种活动的事件探查器跟踪。如果某个 SPID 在一个事务中执行了多个语句,那么 DBCC INPUTBUFFER 输出中仅出现最后一个语句。但是,导致锁定依然存在的原因却可能是较早的某一个命令。利用事件探查器跟踪,可以查看当前事务中某个 SPID 执行的所有命令。可以通过以下步骤将 SQL Server 事件探查器设置为捕获跟踪。1. | 打开 SQL Server 事件探查器。 | ||||||||||||||||||||||||||||||||||||||||||||||||
2. | 在工具菜单上,单击选项。 | ||||||||||||||||||||||||||||||||||||||||||||||||
3. | 确保选中了所有事件类和所有数据列选项。 | ||||||||||||||||||||||||||||||||||||||||||||||||
4. | 单击确定。 | ||||||||||||||||||||||||||||||||||||||||||||||||
5. | 在文件菜单上,指向新建,然后单击跟踪。 | ||||||||||||||||||||||||||||||||||||||||||||||||
6. | 在常规选项卡上,指定跟踪名称和要向其中捕获数据的文件。 | ||||||||||||||||||||||||||||||||||||||||||||||||
7. | 在事件选项卡上,将下列事件类型添加到跟踪中:
此外,还可以包含以下事件,以获得更详细的信息。如果是在大容量的生产环境中运行,可以决定仅使用以上事件,因为它们足以解决阻塞问题。如果包含以下的附加事件,就可以更加容易地快速确定问题的根源,但同时也会增加系统负载与跟踪输出的大小。
|
||||||||||||||||||||||||||||||||||||||||||||||||
8. | 确保数据列选项卡上包含下面各列:开始时间、结束时间、连接 ID、SPID、事件类、文本、整数数据、二进制数据、应用程序名称、NT 用户名以及 SQL 用户名。如果包含了上述第二张表中的附加事件,还需包含以下数据列:持续时间、CPU、读取、写入。 | ||||||||||||||||||||||||||||||||||||||||||||||||
9. | 在筛选器选项卡上,排除 SQL Server 内部异常。在跟踪事件准则框中,选择严重度,然后在最大值框中键入 24。然后单击确定。 有关 SQL Server 发送给客户端的监视错误的详细信息,请参阅以下 Microsoft 知识库文章: 199037 (http://support.microsoft.com/kb/199037/) INF:捕获 SQL Server 发送给客户端的错误信息
|
识别和解决常见阻塞问题
通过检查以上信息,可以确定多数阻塞问题的原因。本文其余内容将讨论如何使用此信息识别与解决一些常见的阻塞问题。本讨论假定您已使用了文章 Q251004(上文已提到)中的阻塞脚本来捕获发生阻塞的 SPID 的相关信息,并且已经用上述事件进行了事件探查器跟踪。查看阻塞脚本输出
• | 检查 sysprocesses 输出以确定阻塞链头。 如果没有为阻塞脚本指定快速模式,则会出现一个标题为“SPIDs at the head of blocking chains”的部分,其中列出了脚本输出中阻塞其他 SPID 的 SPID: SPIDs at the head of blocking chains spid ------ 9 10如果指定了快速选项,则仍可通过查看 sysprocesses 输出来确定阻塞头。以下是一小段 sysprocesses 输出: spid status blocked 9 sleeping 0 10 sleeping 0 11 sleeping 13 12 sleeping 10 13 sleeping 9 14 sleeping 12在本例中,可以看出 SPID 9 与 10 的 blocked 列都为 0,表示它们并未被阻塞,但它们都出现在其他 SPID 的 blocked 列中。这表明 SPID 9 和 10 分别是两个阻塞链的头。 |
||||||||||||||||||||||||||||||||||||||
• | 检查 sysprocesses 输出,以了解位于阻塞链头的 SPID 的相关信息。 检查以下 sysprocesses 字段具有重要意义:
|
||||||||||||||||||||||||||||||||||||||
• | 检查 DBCC INPUTBUFFER 输出。 对于位于阻塞链头或具有非零 waittype 的任意 SPID,阻塞脚本都将执行 DBCC INPUTBUFFER,以确定该 SPID 的当前查询: DBCC INPUTBUFFER FOR SPID 9 EventType Parameters EventInfo -------------- ---------- -------------------------------------------- Language Event 0 update titles set title = title多数情况下,该查询会保留阻塞其他用户的锁。但是,如果 SPID 处于事务中,那么锁就可能由先前执行的查询而不是当前查询获得。因此,还应查看 SPID 的事件探查器输出,而不仅查看 inputbuffer。 注意:因为阻塞脚本包含多个步骤,所以 SPID 可能会作为阻塞链头出现在第一部分,但在执行 DBCC INPUTBUFFER 查询时,它不再阻塞,因此也就没有捕获到该 INPUTBUFFER。这表示针对该 SPID 的阻塞正在自行消失,因此它可能是问题,也可能不是问题。此时,既可以使用阻塞脚本的快速版本,以确保在清除 inputbuffer 之前捕获到它(但我们不担保您一定能做到);也可以查看该时间段内的事件探查器数据,以确定该 SPID 所执行的查询。 |
行 | DatabaseID:FileID:PageID:Slot(row) | RID:5:1:104:3 其中,数据库 ID 5 是 pubs,文件 ID 1 是主数据文件,页 104 是 titles 表中的一个页面,槽 3 指示该行在此页面上的位置。 |
编译 | DatabaseID:ObjectID | TAB:5:834102012 [[COMPILE]] |
其中,数据库 ID 5 是 pubs,但对象 ID 834102012 是一个存储过程。这表明该 SPID 正在等待编译此存储过程的计划。
查看事件探查器数据
在解决阻塞问题时,有效地查看事件探查器数据是非常有用的。最重要的一点是:您不必查看捕获到的所有内容,您只需要选择对自己有用的部分。事件探查器提供了可以帮助您高效地查看已捕获数据的功能。在属性对话框(单击文件菜单上的属性)中,事件探查器允许您限制所显示的数据,方法是:删除数据列或事件,按数据列分组(排序),然后应用筛选器。您可以在整个跟踪或仅在某个列中检索特定值(在编辑菜单上,单击查找)。也可以将事件探查器数据保存到 SQL Server 表中(在文件菜单上,指向另存为,然后单击表),然后对它运行 SQL 查询。注意,应当仅在一个以前保存的跟踪文件上执行筛选。如果是在某个活动跟踪上执行这些步骤,就有可能会丢失该跟踪启动以来捕获的那些数据。首先将活动跟踪保存至某个文件或表中(在文件菜单上,单击另存为),然后重新打开它(在文件菜单上,单击打开),之后再继续。在处理已保存的跟踪文件时,筛选操作不会永久性地删除筛选掉的数据,这些数据只是没有显示而已。您可以根据需要添加和删除事件及数据列,以便集中搜索目标。
查找的内容:
• | 位于阻塞链头的 SPID 在当前事务中执行了哪些命令? 针对位于阻塞链头的特定 SPID,筛选跟踪数据(在文件菜单上,单击属性,然后在筛选器选项卡上指定 SPID 值)。然后,检查它在阻塞其他 SPID 之前所执行的命令。如果包含有事务事件,它们可以轻而易举地识别出事务的开始时间。此外,可以在 Text 列中搜索 BEGIN、SAVE、COMMIT 或 ROLLBACK TRANSACTION 操作。使用 sysprocesses 表中的 open_tran 值确保您已捕获所有事务事件。了解已执行的命令和事务上下文,以便确定 SPID 保留锁的原因。 请记住,事件和数据列都可以删除。不要同时查看开始和完成的事件,您需要选择其中一个。如果阻塞的 SPID 不是存储过程,请删除 SP:Starting 或 SP:Completed 事件;SQLBatch 和 RPC 事件将显示该过程调用。只有在需要查看该级别的详细信息时才查看 SP 事件。 |
• | 位于阻塞链头的 SPID 查询的持续时间是多少? 如果包含了上述已完成的事件,则 Duration 列将显示该查询的执行时间。它有助于识别导致阻塞的长期运行的查询。要确定查询执行速度慢的原因,请依次查看 CPU、读取和写入列以及执行计划事件。 |
对常见阻塞情况分类
下表列出了常见症状及其可能的原因。Scenario 列中的数字与下文“常见阻塞情况和解决方案”部分中的数字相对应。Waittype、Open_Tran 和 Status 列都是 sysprocesses 信息。Resolves? 列指示阻塞是否会自行消失。Scenario | Waittype | Open_Tran | Status | Resolves? | 其他症状 |
---|---|---|---|---|---|
1 | Non-zero | >= 0 | runnable | 当查询完成时会自行消失。 | Physical_IO、CPU 和/或 Memusage 列将随时间的推移而增大。当完成时,查询的持续时间将很长。 |
2 | 0x0000 | >0 | sleeping | 不会,但可以终止 SPID。 | 此 SPID 的事件探查器跟踪中可能会出现关注信号,这表明发生了查询超时或取消。 |
3 | 0x0000 | >= 0 | runnable | 不会,直到客户端获取了所有行或关闭连接时才会消失。SPID 可以终止,但可能需要等待多达 30 秒的时间。 | 如果 open_tran = 0,并且当事务隔离级别为默认值 (READ COMMMITTED) 时该 SPID 就会保留锁,这可能是一个原因。 |
4 | Varies | >= 0 | runnable | 不会,直到客户端取消查询或关闭连接时才会消失。SPID 可以终止,但可能需要等待多达 30 秒的时间。 | 对于阻塞链头的 SPID 而言,sysprocesses 中的 hostname 列与它所阻塞的某个 SPID 的相同。 |
5 | 0x0000 | >0 | rollback | 会。 | 此 SPID 的事件探查器跟踪中可能会出现关注信号,表明出现了查询超时或取消,或仅发出了一个回滚语句。 |
6 | 0x0000 | >0 | sleeping | 最终,当 Windows NT 确定该会话不再处于活动状态时,该 SQL Server 连接将会断开。 | sysprocesses 中的 last_batch 值比当前时间早很多。 |
常见阻塞情况和解决方案
下面所列情况将具有上表列出的特征。此部分提供其他详细信息(若适用)以及解决方案。1. | 正常运行,但执行时间长的查询所导致的阻塞。 解决方案: 解决此类阻塞问题的方法是设法优化查询。实际上,此类阻塞问题可能只是性能问题,您需要按照这个思路进行处理。有关解决特定查询运行缓慢问题的信息,请参阅以下 Microsoft 知识库文章: 243589 (http://support.microsoft.com/kb/243589/) 如何解决 SQL Server 7.0 或更高版本上的查询低性能问题
有关解决应用程序整体性能问题的信息,请参阅以下 Microsoft 知识库文章:
224587 (http://support.microsoft.com/kb/224587/) 如何解决 SQL Server 应用程序的性能问题
如果有一个长期运行的查询不仅阻塞了其他用户,而且无法优化,则可以考虑将它从 OLTP 环境移动到决策支持系统中。 |
||||||||||||
2. | 丧失了对事务嵌套层跟踪的休眠 SPID 导致的阻塞 此类阻塞通常可以由以下 SPID 识别:它正在休眠或等待命令,但其事务嵌套层(@@TRANCOUNT、sysprocesses 中的 open_tran)却大于零。如果应用程序遇到查询超时,或在没有发出所需数量的 ROLLBACK 和/或 COMMIT 语句的情况下发出了取消命令,就会发生这种情况。当 SPID 收到查询超时或取消时,它会终止当前查询和批处理,但不会自动回滚或提交事务。这是应用程序造成的,因为 SQL Server 不会认为仅由于取消了一个查询,就必须要回滚整个事务。在事件探查器跟踪中,查询超时或取消将显示为 SPID 的一个 ATTENTION 信号事件。 为证明这一点,请从查询分析器发出以下查询示例:
当查询处于执行状态时,单击红色的取消按钮。当取消查询后,SELECT @@TRANCOUNT 会指出事务嵌套层为 1。如果它是 DELETE 或 UPDATE 查询,或在 SELECT 上使用了 HOLDLOCK,那么依然会保留所有已获得的锁。即使对于上述查询,如果在该事务的早些时候已经有另一个查询获得并保留了锁,那么当取消以上 SELECT 时,这些锁依然会被保留。解决方案:
|
||||||||||||
3. | 由其对应客户端应用程序没有提取所有结果行以完成操作的 SPID 导致的阻塞 当向服务器发送查询后,所有应用程序都必须立即提取所有结果行以完成操作。如果某个应用程序没有提取所有结果行,锁依然会保留在表中,进而阻塞其他用户。如果所使用的应用程序是以透明方式向服务器提交 SQL 语句,则该应用程序必须提取所有结果行。如果它没有提取所有结果行(并且无法通过配置让它完成此任务),就可能无法解决阻塞问题。为了避免发生这样的问题,可以将行为不佳的应用程序限制到某个报告或决策支持数据库中。 解决方案: 重新编写该应用程序,以便能提取所有结果行以完成操作。 |
||||||||||||
4. | 分布式客户端/服务器死锁导致的阻塞 与常见死锁不同的是,分布式死锁不能用 RDBMS 锁管理器检测出来。这是因为,该死锁涉及的资源中实际上仅有一个是 SQL Server 锁。该死锁的其他部分都属于 SQL Server 无法控制的客户端应用程序级别。以下两个示例展示了这种问题的发生环境,以及为了避免问题发生,应用程序可以采取的措施。
解决方案: 可以使用两种可靠的解决方案:查询超时或绑定连接。
|
||||||||||||
5. | 由处于“金色”或回滚状态的 SPID 导致的阻塞 在用户定义的事务外被“终止”或取消的数据修改查询将会回滚。作为客户端计算机重启及其网络会话断开连接的附带影响,也会发生这一现象。同样,作为死锁牺牲品而被选中的查询也会回滚。回滚数据修改查询的速度通常要慢于最初应用更改的速度。例如,如果 DELETE、INSERT 或 UPDATE 语句已经运行了一个小时,那么其回滚至少也需要一个小时。这是预期行为,因为所做更改必须全部回滚,否则数据库的事物完整性和物理完整性将会受损。因为必然要进行完整回滚,所以 SQL Server 会将 SPID 标记为处于“金色”或回滚状态(这意味着它无法被“终止”或选作死锁牺牲品)。一般情况下,可以通过观察 sp_who 的输出(它可以指示 ROLLBACK 命令)来识别该状态。sysprocesses 的Status 列会指示 ROLLBACK 状态,它也会出现在 sp_who 输出中或 SQL 企业管理器当前的活动屏幕上。 解决方案: 您必须等待 SPID 回滚完所做的更改。 如果在执行该操作期间关闭服务器,数据库将在重启时进入恢复模式,并且在处理完所有打开的事务前,您将无法访问该数据库。每项事务的启动恢复时间与运行时恢复时间基本相同,并且在启动恢复期间您无法访问数据库。因此,强制服务器关闭以处理处于回滚状态的 SPID,通常达不到预期效果。 为了避免这种状况,请勿在 OLTP 系统繁忙阶段执行大批的 INSERT、UPDATE 或 DELETE 操作。请尽可能在低活动期执行此类操作。 |
||||||||||||
6. | 由孤立连接导致的阻塞 如果客户端应用程序陷阱或客户端工作站重新启动,则某些情况下可能无法立即取消与服务器的网络会话。从服务器的角度来看,客户端似乎依然存在,所获取的所有锁也可能会依然保留。有关详细信息,请参阅《SQL Server 7.0 联机丛书》中的“孤立连接”主题。 解决方案: 如果客户端应用程序在没有妥善清理其资源的情况下断开了连接,可以使用 KILL 命令终止该 SPID。该 KILL 命令将以 SPID 值作为输入。例如,要终止 SPID 9,只需要发出以下命令:
注意:由于 KILL 命令存在检查间隔,因此完成 KILL 命令需要花费 30 秒钟。 |
应用程序与阻塞问题
当面临阻塞问题时,有一种倾向总是把重点放在服务器端调整和平台问题上。但是,这样做通常并不能解决问题,而且会浪费时间和精力,最好的方法是检查客户端应用程序和它提交的查询。就生成的数据库调用而言,无论应用程序提供的可见性级别如何,要解决阻塞问题,通常既需要检查应用程序所提交的 SQL 语句,还需要检查应用程序在查询取消、连接管理、提取所有结果行等方面的确切行为。如果开发工具不允许对连接管理、查询取消、查询超时、结果提取等内容进行显示控制,就可能无法解决阻塞问题。在为 SQL Server 选择应用程序开发工具之前,必须先仔细检查是否存在这种可能性,对于业务关键型 OLTP 环境尤其需要如此。在数据库和应用程序的设计与构建过程中,请务必小心谨慎。尤其需要对每个查询在资源消耗、隔离级别和事务路径长度方面进行评估。每个查询和事务应尽可能的轻便。需要执行良好的连接管理规定。如果做不到这一点,应用程序就会在用户数量减少时表现出具有可接受的性能,而在用户数量增加时,性能可能会明显下降。
如果应用程序和查询设计恰当,Microsoft SQL Server 有能力在一台服务器上同时支持数千名用户,并且很少出现阻塞。有关详细信息,请参阅《SQL Server 7.0 联机丛书》中的“应用程序设计”和“了解和避免阻塞”主题。可以成功地为如此数量的用户提供服务的站点,通常使用的就是这些主题中介绍的技术。