上述文章中包含的多数信息都已更新,用户可以在《SQL Server 7.0 联机丛书》的“了解和避免阻塞”主题下找到此类信息。在继续阅读本文之前,请仔细回顾该信息,此处不再赘述。本文主要讲述如何监视 SQL Server 捕获相关系统信息,以及如何分析此信息以便成功解决阻塞问题。
对于任何使用锁定式并发的关系数据库管理系统 (RDBMS) 而言,阻塞都是一项不可避免的特性。在 SQL Server 上,如果一个 SPID 锁定了某特定资源,而第二个 SPID 试图在同一资源上获取相冲突的锁类型,则会发生阻塞。通常,第一个 SPID 仅将资源锁定很短时间。当它解除锁定后,第二个连接即可自由地在该资源上获取自己的锁,并继续执行操作。这是一种正常行为,一天之中可能会发生许多次,而不会对系统性能造成任何明显的影响。
一个查询的持续时间和事务上下文将决定其锁定时限,进而决定了它们对其他查询的影响。如果查询不在事务内执行(并且没有使用任何锁提示),那么对于 SELECT 语句而言,仅在实际读取某一资源时才会锁定该资源,但查询期间并不锁定该资源。而对于 INSERT、UPDATE 和 DELETE 语句,会在查询期间进行锁定,其目的在于实现数据一致性并允许在必要时进行回滚查询。
如果查询是在事务内执行的,那么锁的保留时间将由以下因素决定:查询类型、事务隔离级别、查询中是否使用了锁提示。有关锁定说明、锁提示和事务隔离级别的信息,请参阅《SQL Server 7.0 联机丛书》中的以下主题:
| • | “了解 SQL Server 中的锁定功能” |
| • | “锁定体系结构” |
| • | “锁兼容性” |
| • | “锁定提示” |
| • | “更改 Oracle 和 SQL Server 中的默认锁定行为” |
当锁定和阻塞增加到对系统性能产生不利影响时,其原因通常有:
| • | 某个 SPID 将一组资源锁定了较长一段时间后,才释放这些资源。此类阻塞会随着时间推移而自行消失,但会导致系统性能降低。 |
| • | SPID 锁定了一组资源,并且不再释放这些资源。此类阻塞不会自行消失,它会无限期地妨碍对受影响资源的访问。 |
在上述第一种情况中,当 SPID 解除锁定后,阻塞问题会随着时间推移而自行消失。但是,情况会千变万化,因为随着时间的流逝,不同的 SPID 会在不同的资源上导致阻塞,进而产生变化的目标。为此,在上述情况下,用户很难通过 SQL Server 企业管理器或单独的 SQL 查询来解决问题。第二种情况会导致一种不一致的状态,这种状态比较容易诊断。
收集阻塞信息
为了降低阻塞问题的解决难度,数据库管理员可以使用持续监视 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 企业管理器识别阻塞链头,方法如下:
| a. | 展开服务器组;然后展开服务器。 | | b. | 展开管理;然后展开当前活动。 | | c. | 展开锁/进程 ID。详细信息窗格中将显示 SPID 及其阻塞信息。正在阻塞其他 SPID 的 SPID 将显示为“(阻塞)”。 | 注意,有时需要使用查询而不是企业管理器,因为某些类型的 tempdb 阻塞问题可能会阻止运行采用临时表操作的查询。使用直接查询,可以为您提供必要的控制能力,以避免发生此类问题。 |
| 2. | 查找发生阻塞的 SPID 正在运行的查询。 脚本方法使用以下查询确定特定 SPID 发出的命令:
DBCC INPUTBUFFER ()
也可以使用 SQL 企业管理器,方法如下:| a. | 展开服务器组;然后展开服务器。 | | b. | 展开管理;然后展开当前活动。 | | c. | 单击进程信息。详细信息窗格中将显示 SPID。 | | d. | 双击发生阻塞的 SPID,以查看该 SPID 执行的上批 Transact-SQL 命令。 |
|
| 3. | 查找发生阻塞的 SPID 正使用的锁的类型。 可以通过执行 sp_lock 系统存储过程,来确定该信息。也可以使用企业管理器,方法如下:
| a. | 展开服务器组;然后展开服务器。 | | b. | 展开管理;然后展开当前活动。 | | c. | 展开锁/进程 ID。详细信息窗格中将显示 SPID 以及正使用的锁的相关信息。 |
|
| 4. | 查找发生阻塞的 SPID 的事务嵌套层和进程状态。 @@TRANCOUNT 全局变量中提供有 SPID 的事务嵌套层。但是,也可以通过查询 sysprocesses 表,从 SPID 外部确定该信息,方法如下:
SELECT open_tran FROM SYSPROCESSES WHERE SPID=
go
返回的值是该 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. | 在事件选项卡上,将下列事件类型添加到跟踪中:
| 标题 | 要添加的事件 | 说明 |
|---|
| 错误和警告 | Exception | 此事件表示出现了异常。严重度低于 25 的异常表明 SQL Server 向客户端返回了一个错误。严重度为 25 的异常为 SQL Server 内部异常,如下所述,应筛选此类异常。 | | 杂项 | Attention | 此事件表示出现了关注信号。出现关注信号的常见原因是存在客户端撤消或查询超时现象。 | | 会话 | Connect | 此事件表示已创建一个新的连接。 | | 会话 | Disconnect | 此事件表示有一个客户端已断开连接。 | | 会话 | Existing Connection | 此事件表示启动 SQL 事件探查器跟踪时存在一个连接。 | | TSQL | RPC:Starting | 此事件表示远程过程调用 (RPC) 已开始执行。 | | TSQL | SQL:BatchStarting | 此事件表示 Transact-SQL 批处理已开始执行。 | | 存储过程 | SP:StmtStarting | 此事件表示存储过程中的语句开始执行的时间。存储过程名称显示在该事件的文本的开头。 |
此外,还可以包含以下事件,以获得更详细的信息。如果是在大容量的生产环境中运行,可以决定仅使用以上事件,因为它们足以解决阻塞问题。如果包含以下的附加事件,就可以更加容易地快速确定问题的根源,但同时也会增加系统负载与跟踪输出的大小。
| 标题 | 要添加的事件 | 说明 |
|---|
| 杂项 | Execution Plan | 此事件显示已执行的 Transact-SQL 语句的计划树。 | | 事务 | DTCTransaction | 此事件跟踪两个(或多个)数据库或服务器之间的 Microsoft 分布式事务处理协调器 (MS DTC) 事务。 | | 事务 | SQLTransaction | 此事件跟踪 SQL BEGIN、SAVE、COMMIT 和 ROLLBACK TRANSACTION 语句。 | | TSQL | RPC:Completed | 此事件表示已执行完远程过程调用 (RPC)。 | | TSQL | SQL:BatchCompleted | 此事件表示已执行完 Transact-SQL 批处理。 | | 存储过程 | SP:StmtCompleted | 此事件表示已执行完存储过程中的语句。 |
|
| 8. | 确保数据列选项卡上包含下面各列:开始时间、结束时间、连接 ID、SPID、事件类、文本、整数数据、二进制数据、应用程序名称、NT 用户名以及 SQL 用户名。如果包含了上述第二张表中的附加事件,还需包含以下数据列:持续时间、CPU、读取、写入。 |
| 9. | 在筛选器选项卡上,排除 SQL Server 内部异常。在跟踪事件准则框中,选择严重度,然后在最大值框中键入 24。然后单击确定。
有关 SQL Server 发送给客户端的监视错误的详细信息,请参阅以下 Microsoft 知识库文章: 199037 (http://support.microsoft.com/kb/199037/) INF:捕获 SQL Server 发送给客户端的错误信息 |
有关事件探查器的使用信息,请参阅《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 字段具有重要意义:
| • | 状态 使用此列可快速了解特定 SPID 的状态。通常情况下,sleeping 状态表示该 SPID 已执行完,正在等待应用程序提交另一项查询或批处理。runnable 状态表示该 SPID 目前正在处理查询。下表简要解释了各种状态值。
| 状态 | 含义 |
|---|
| Background | SPID 正在执行后台任务。 | | Sleeping | SPID 当前并未执行。它通常表示该 SPID 正在等待应用程序发出命令。 | | Runnable | SPID 当前正在执行。 | | Dormant | 类似于 Sleeping,但 Dormant 还表示 SPID 在完成一个 RPC 事件后已被重置。重置操作清除了执行 RPC 事件过程中使用的资源。这是一种正常状态,SPID 不仅可用,并正在等待执行后续命令。 | | Rollback | 该 SPID 处于事务回滚状态。 | | Defwakeup | 表示 SPID 正在等待处于释放过程的资源。waitresource 字段应表示正被讨论的资源。 | | Spinloop | 进程在尝试获取用于 SMP 系统上的并发控制的 spinlock 时正处于等待状态。 |
| | • | Open_tran 该字段显示了 SPID 的事务嵌套层。如果此值大于 0,表明 SPID 处于打开的事务中,并且可能正在使用由该事务中的任意语句获取的锁。 | | • | Lastwaittype、waittype 和 waittime lastwaittype 字段显示了 SPID 的上一个或当前 waittype。该字段是 SQL Server 7.0 中的新字段,是 waittype 字段(保留的内部二进制列)的字符串表示形式。如果 waittype 是 0x0000,则表明 SPID 当前未处于等待状态,lastwaittype 值表示该 SPID 的上一个 waittype。如果 waittype 非零,则 lastwaittype 值表示 SPID 的当前 waittype。
有关其他 lastwaittype 和 waittype 值的简要说明,请参阅以下 Microsoft 知识库文章: 244455 (http://support.microsoft.com/kb/244455/) INF:SQL Server 7.0 中 sysprocesses waittype 和 lastwaittype 列的定义 在确定 SPID 是否处于进程中时,waittime 将十分有用。如果针对 sysprocesses 表的查询返回了 waittime 列中的一个值,并且该值小于从 sysprocesses 的上一个查询所获得的 waittime 值,则表明已获取并释放了先前的锁,目前正在等待新锁(假定 waittime 非零)。这一点可以通过比较 waitresource 和 sysprocesses 输出来验证。 | | • | Waitresource 该字段表示 SPID 正在等待的资源。下表列出了 waitresource 的常见格式及其含义:
| 资源 | 格式 | 示例 |
|---|
| 表 | DatabaseID:ObjectID | TAB:5:261575970 其中,数据库 ID 5 是 pubs 示例数据库,对象 ID 261575970 是 titles 表。 | | 页 | DatabaseID:FileID:PageID | PAG:5:1:104 其中,数据库 ID 5 是 pubs,文件 ID 1 是主数据文件,页 104 是 titles 表中的一个页面。 | | 键 | DatabaseID:ObjectID:IndexID (索引键的哈希值) | KEY:5:261575970:1 (5d0164fb1eac) 其中,数据库 ID 5 是 pubs,对象 ID 261575970 是 titles 表,索引 ID 1 是聚集索引,而哈希值表示特定行的索引键值。 |
| | • | 其他列 其余 sysprocesses 列可让您了解问题的根源。其有效性因问题环境而异。例如,可以确定当 SPID 提交的上一批次为 (last_batch) 时,问题是否仅源于特定网络库 (net_library) 上的某些客户端(主机名)等。有关所有 sysprocesses 列的简要说明,请参阅《SQL Server 7.0 联机丛书》中的“sysprocesses (T-SQL)”主题。
注意:阻塞脚本输出中不包含 SUID 列,因为它是派生列,包含它的唯一目的是为了实现向后兼容。SQL Server 不会在内部使用它,如果对它进行查询,就会导致性能降低(因为它是派生的),所以没有包含它。 |
|
| • | 检查 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 信号事件。
为证明这一点,请从查询分析器发出以下查询示例:
BEGIN TRAN
SELECT * FROM SYSOBJECTS S1, SYSOBJECTS S2
-- Issue this after canceling query
SELECT @@TRANCOUNT
ROLLBACK TRAN
当查询处于执行状态时,单击红色的取消按钮。当取消查询后,SELECT @@TRANCOUNT 会指出事务嵌套层为 1。如果它是 DELETE 或 UPDATE 查询,或在 SELECT 上使用了 HOLDLOCK,那么依然会保留所有已获得的锁。即使对于上述查询,如果在该事务的早些时候已经有另一个查询获得并保留了锁,那么当取消以上 SELECT 时,这些锁依然会被保留。
解决方案:| • | 应用程序必须妥善管理事务嵌套层,否则,在取消查询后,它们可能会导致此类阻塞问题。可以通过以下方法之一解决此问题: | a. | 在客户端应用程序的错误处理程序中,请在出现任意错误后提交一个 IF @@TRANCOUNT > 0 ROLLBACK TRAN,即使客户端应用程序并不认为事务处于打开状态也是如此。之所以要这样做的原因是:在批处理过程中调用的存储过程,可以在客户端应用程序不知道的情况下启动事务。注意,某些条件(如取消查询)会妨碍该过程执行当前语句,因此,即使该过程具有检查 IF @@ERROR <> 0 与中止事务的逻辑,在此类情况下也不会执行该回滚代码。 | | b. | 使用 SET XACT_ABORT ON 进行连接,或在启动了事务并且在出现错误后未进行清理的任何存储过程中使用它。当发生运行时错误时,该设置会中止所有打开的事务并将控制权交还给客户端。注意,不执行导致该错误的语句后的 T-SQL 语句。 | | c. | 如果打开了连接,并在将连接释放回池之前运行了少量查询的应用程序(如基于 Web 的应用程序)中使用了连接池,那么在修改客户端连接以妥善处理错误之前,临时禁用连接池可能有助于缓解问题。禁用连接池并释放连接将物理注销 SQL Server 连接,进而导致服务器回滚所有打开的事务。 | | d. | 如果启用了连接池并且目标服务器是 SQL Server 2000,那么将客户端计算机升级到 MDAC 2.6 或更高版本可能会大有益处。此版本的 MDAC 组件可以在 ODBC 驱动程序和 OLE DB 提供程序中添加代码,这样,连接就可以在再次使用前“重置”。对 sp_reset_connection 的这一调用会中止任何服务器发起的事务(由客户端 app 发起的 DTC 事务不受影响)、重置默认的数据库和 SET 选项等。注意,连接池中的连接不会被重置,除非再次使用它,因此有可能发生以下情况:用户可以打开一个事务,然后将连接释放到连接池,但该连接可能在几秒钟内都没有被再次使用,在此期间该事务将保持打开状态。如果该连接没有被再次使用,则当连接超时并被从连接池中删除后,该事务将中止。所以,客户端应用程序最好在事务的错误处理程序中中止事务,或使用 SET XACT_ABORT ON 来避免这一潜在延迟。 |
| | • | 实际上,此类阻塞问题可能也是性能问题,您需要按照这个思路进行处理。如果可以减少查询执行时间,就不会发生查询超时或取消。当发生超时或取消情况时,应用程序应当有能力进行处理,这一点很重要。但检查查询性能也可能对您有所帮助。
有关解决特定查询运行缓慢问题的信息,请参阅以下 Microsoft 知识库文章: 243589 (http://support.microsoft.com/kb/243589/) 如何解决 SQL Server 7.0 或更高版本上的查询低性能问题 有关解决应用程序整体性能问题的信息,请参阅以下 Microsoft 知识库文章: 224587 (http://support.microsoft.com/kb/224587/) 如何解决 SQL Server 应用程序的性能问题 如果有一个长期运行的查询不仅阻塞了其他用户,而且无法优化,则可以考虑将它从 OLTP 环境移动到决策支持系统中。 |
|
| 3. | 由其对应客户端应用程序没有提取所有结果行以完成操作的 SPID 导致的阻塞
当向服务器发送查询后,所有应用程序都必须立即提取所有结果行以完成操作。如果某个应用程序没有提取所有结果行,锁依然会保留在表中,进而阻塞其他用户。如果所使用的应用程序是以透明方式向服务器提交 SQL 语句,则该应用程序必须提取所有结果行。如果它没有提取所有结果行(并且无法通过配置让它完成此任务),就可能无法解决阻塞问题。为了避免发生这样的问题,可以将行为不佳的应用程序限制到某个报告或决策支持数据库中。
解决方案:
重新编写该应用程序,以便能提取所有结果行以完成操作。 |
| 4. | 分布式客户端/服务器死锁导致的阻塞
与常见死锁不同的是,分布式死锁不能用 RDBMS 锁管理器检测出来。这是因为,该死锁涉及的资源中实际上仅有一个是 SQL Server 锁。该死锁的其他部分都属于 SQL Server 无法控制的客户端应用程序级别。以下两个示例展示了这种问题的发生环境,以及为了避免问题发生,应用程序可以采取的措施。
| a. | 使用单客户端线程的客户端/服务器分布式死锁 如果客户端具有多个打开的连接和一个执行线程,则可能发生以下分布式死锁。为了简便起见,此处使用“dbproc”一词指代客户端连接结构。
SPID1------blocked on lock------->SPID2
/ (正在等待将结果写回
| 客户端)
| |
| | Server side
| ================================|==================================
| <-- single thread --> | Client side
| /
dbproc1 <------------------- dbproc2
(正在等待提取 (被 dbproc1 有效的阻止,正在等待
下一行) 要运行的单个执行线程)
在上例中,单客户端应用程序线程具有两个打开的连接。它在 dbproc1 上异步提交了一个 SQL 操作。这意味着它不等调用返回就继续执行下一步操作。然后,该应用程序又在 dbproc2 上提交了另一个 SQL 操作,并等待结果以开始处理返回的数据。当数据开始返回时(无论哪个 dbproc 先响应,假定是 dbproc1),它都会处理完成该 dbproc 上返回的所有数据。它将从 dbproc1 提取结果,直到 SPID1 被 SPID2 使用的锁阻塞为止(因为这两个查询是以异步方式在服务器上运行的)。此时,dbproc1 将无限期地等待更多数据。SPID2 并未被锁阻塞,而是在尝试向它的客户端 dbproc2 发送数据。但是,由于应用程序的单个执行线程正由 dbproc1 使用,因此在应用程序层,dbproc2 会被 dbproc1 有效地阻塞。这样就会产生 SQL Server 无法检测或解决的死锁问题,因为所涉及的资源中仅有一个是 SQL Server 资源。 | | b. | 每个连接各用一个线程时的客户端/服务器分布式死锁
即使客户端上的每个连接都存在一个独立的线程,仍可能会发生如下所示的该分布式死锁的变体。
SPID1------blocked on lock-------->SPID2
/ (正在等待网络写入) Server side
| |
| |
| INSERT |SELECT
| ================================|==================================
| <-- thread per dbproc --> | Client side
| /
dbproc1 <-----data row------- dbproc2
(正在等待 (被 dbproc1 阻塞,正在等待它
插入) 以便从其缓冲区中读取行)
该例与示例 A 类似,不同之处在于:dbproc2 和 SPID2 都在运行 SELECT 语句,目的是执行逐行处理并通过缓冲区将每行传递给 dbproc1,以便在相同表中执行 INSERT、UPDATE 或 DELETE 语句。最后,SPID1(执行 INSERT、UPDATE 或 DELETE)将被 SPID2(执行 SELECT)使用的锁阻塞。SPID2 会将一个结果行写入客户端 dbproc2。然后,Dbproc2 将尝试把缓冲区中的行传递给 dbproc1,但却发现 dbproc1 处于忙碌状态(由于要等待 SPID1 完成当前的 INSERT,所以导致 dbproc1 被阻塞,而 SPID1 则被 SPID2 阻塞)。此时,在应用程序层,dbproc2 会被 dbproc1 阻塞,而在数据库级别,dbproc1 的 SPID (SPID1) 会被 SPID2 阻塞。这种情况会再次导致 SQL Server 无法检测到或解决的死锁问题,原因是所涉及资源中仅有一个是 SQL Server 资源。 | 示例 A 和示例 B 都是应用程序开发人员必须知晓的基本问题。他们必须对应用程序进行编码,以便妥善处理这类问题。
解决方案:
可以使用两种可靠的解决方案:查询超时或绑定连接。
| • | 查询超时 当采用查询超时时,如果出现分布式死锁,那么在发生超时时将会中断。有关使用查询超时的详细信息,请参阅 DB-Library 或 ODBC 文档。 | | • | 绑定连接 具有多个连接的客户端可以使用该功能将这些连接绑定到一个事务空间内,以避免连接相互阻塞。有关详细信息,请参阅《SQL Server 7.0 联机丛书》中的“使用绑定连接”主题。 |
|
| 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 9
注意:由于 KILL 命令存在检查间隔,因此完成 KILL 命令需要花费 30 秒钟。 |
应用程序与阻塞问题
当面临阻塞问题时,有一种倾向总是把重点放在服务器端调整和平台问题上。但是,这样做通常并不能解决问题,而且会浪费时间和精力,最好的方法是检查客户端应用程序和它提交的查询。就生成的数据库调用而言,无论应用程序提供的可见性级别如何,要解决阻塞问题,通常既需要检查应用程序所提交的 SQL 语句,还需要检查应用程序在查询取消、连接管理、提取所有结果行等方面的确切行为。如果开发工具不允许对连接管理、查询取消、查询超时、结果提取等内容进行显示控制,就可能无法解决阻塞问题。在为 SQL Server 选择应用程序开发工具之前,必须先仔细检查是否存在这种可能性,对于业务关键型 OLTP 环境尤其需要如此。
在数据库和应用程序的设计与构建过程中,请务必小心谨慎。尤其需要对每个查询在资源消耗、隔离级别和事务路径长度方面进行评估。每个查询和事务应尽可能的轻便。需要执行良好的连接管理规定。如果做不到这一点,应用程序就会在用户数量减少时表现出具有可接受的性能,而在用户数量增加时,性能可能会明显下降。
如果应用程序和查询设计恰当,Microsoft SQL Server 有能力在一台服务器上同时支持数千名用户,并且很少出现阻塞。有关详细信息,请参阅《SQL Server 7.0 联机丛书》中的“应用程序设计”和“了解和避免阻塞”主题。可以成功地为如此数量的用户提供服务的站点,通常使用的就是这些主题中介绍的技术。