当来自应用程序的第一个连接控制锁而第二个连接需要相冲突的锁类型时,将发生阻塞。其结果是强制第二个连接等待,而在第一个连接上阻塞。不管是来自同一应用程序还是另外一台客户机上单独的应用程序,一个连接都可以阻塞另一个连接。
说明 一些需要锁保护的操作可能不明显,例如系统目录表和索引上的锁。
大多数阻塞问题的发生是因为一个进程控制锁的时间过长,导致阻塞的进程链都在其它进程上等待锁。
常见的阻塞情形包括:
提交执行时间长的查询。
长时间运行的查询会阻塞其它查询。例如,影响很多行的 DELETE 或 UPDATE 操作能获取很多锁,这些锁不论是否升级到表锁都阻塞其它查询。因此,一般不要将长时间运行的决策支持查询和联机事务处理 (OLTP) 查询混在一起。解决方案是想办法优化查询,如更改索引、将大的复杂查询分成简单的查询或在空闲时间或单独的计算机上运行查询。
查询运行时间长并由此导致阻塞的一个原因是这些查询不适当地使用游标。游标可能是在结果集中浏览的便利方法,但使用游标可能比使用面向集合的查询慢。
取消没有提交或回滚的查询。
如果应用程序取消查询(如使用开放式数据库连接 (ODBC) sqlcancel 函数)但没有同时发出所需数目的 ROLLBACK 和 COMMIT 语句,则会发生这种情况。取消查询并不自动回滚或提交事务。取消查询后,所有在事务内获取的锁都将保留。应用程序必须提交或回滚已取消的事务,从而正确地 管理事务嵌套级。
应用程序没处理完所有结果。
将查询发送到服务器后,所有应用程序必须立即完成提取所有结果行。如果应用程序没有提取所有结果行,锁可能会留在表上而阻塞其他用户。如果使用的应用程序 将 Transact-SQL 语句透明地提交给服务器,则该应用程序必须提取所有结果行。如果应用程序没这样做(如果无法配置它执行此操作),则可能无法解决阻塞问题。为避免此问题, 可以将这些应用程序限制在报表或决策支持数据库上。
分布式客户端/服务器死锁。
与常规死锁不同,分布式死锁无法由 Microsoft® SQL Server™ 2000 自动检测到。如果应用程序打开多个与 SQL Server 的连接并异步提交查询,则可能会发生分布式客户端/服务器死锁。
例如,一个客户端应用程序线程有两个开放式连接。该线程异步启动事务并在第一个连接上发出查询。应用程序随后启动其它事务,在另一个连接上发出查询并等待 结果。当 SQL Server 返回其中一个连接的结果时,应用程序开始处理这些结果。应用程序就这样处理结果,直到生成结果的查询被另一个连接上执行的查询阻塞而导致再没有可用的结果 为止。此时第一个连接阻塞,无限期等待处理更多的结果。第二个连接没有在锁上阻塞,但仍试图将结果返回给应用程序。然而,由于应用程序阻塞而在第一个连接 上等待结果,第二个连接的结果将得不到处理。
若要避免此问题,请执行下列任一操作:
对每个查询使用查询超时。
对每个查询使用锁定超时。有关更多信息,请参见自定义锁超时。
使用绑定连接。有关更多信息,请参见使用绑定连接。
SQL Server 本质上是受客户端应用程序操纵的傀儡。客户端应用程序对服务器上获取的锁几乎有完全的控制(并对锁负责)。虽然 SQL Server 锁管理器自动使用锁保护事务,但这受客户端应用程序发出的查询类型和对结果的处理方式的直接鼓动。因此,大多数阻塞问题的解决方案都涉及检查客户端应用程 序。
阻塞问题常要求检查应用程序提交的 SQL 语句本身,以及检查与连接管理、所有结果行的处理等有关的应用程序行为本身。如果开发工具不允许显式控制连接管理、查询超时、结果处理等,阻塞问题可能得不到解决。
设计应用程序以避免阻塞的准则包括:
不要使用或设计使用户得以填写编辑框的应用程序,编辑框会生成长时间运行的查询。例如,不要使用或设计提示用户输入的应用程序,允许某些字段保留空白或允许输入通配符。这可能导致应用程序提交运行时间过长的查询,从而导致阻塞问题。
不要使用或设计使用户得以在事务内输入内容的应用程序。
允许取消查询。
使用查询或锁定超时,防止失控查询和避免分布式死锁。
立即完成提取所有结果行。
使事务尽可能简短。
显式控制连接管理。
在所预计的并发用户全负荷下对应用程序进行应力测试。
2.死锁
当某组资源的两个或多个线程之间有循环相关性时,将发生死锁。
死锁是一种可能发生在任何多线程系统中的状态,而不仅仅发生在关系数据库管理系统中。多线程系统中的一个线程可能获取一个或多个资源(如锁)。如果正获取 的资源当前为另一线程所拥有,则第一个线程可能必须等待拥有线程释放目标资源。这时就说等待线程在那个特定资源上与拥有线程有相关性。
如果拥有线程需要获取另外一个资源,而该资源当前为等待线程所拥有,则这种情形将成为死锁:在事务提交或回滚之前两个线程都不能释放资源,而且它们因为正 等待对方拥有的资源而不能提交或回滚事务。例如,运行事务 1 的线程 T1 具有 Supplier 表上的排它锁。运行事务 2 的线程 T2 具有 Part 表上的排它锁,并且之后需要 Supplier 表上的锁。事务 2 无法获得这一锁,因为事务 1 已拥有它。事务 2 被阻塞,等待事务 1。然后,事务 1 需要 Part 表的锁,但无法获得锁,因为事务 2 将它锁定了。事务在提交或回滚之前不能释放持有的锁。因为事务需要对方控制的锁才能继续操作,所以它们不能提交或回滚。
说明 死锁经常与正常阻塞混淆。当一个事务锁定了另一个事务需要的资源,第二个事务等待锁被释放。默认情况下,SQL Server 事务不会超时(除非设置了 LOCK_TIMEOUT)。第二个事务被阻塞,而不是被死锁。
阻塞是死锁的前兆,非正常阻塞是引起死锁的原因.
编写有效的事务
尽可能使事务保持简短很重要。启动事务后,DBMS 必须将很多资源控制到事务结束时,以保护事务的 ACID 属性。如果修改数据,则必须用排它锁保护修改过的行,以防止任何其它事务读取该行,并且必须将排它锁控制到提交或回滚事务时为止。根据事务隔离级别设置, SELECT 语句可以获取必须被控制到提交或回滚事务时为止的锁。特别是在有很多用户的系统中,必须尽可能使事务保持简短以减少并发连接间的资源锁定争夺。在有少量用 户的系统中,运行时间长、效率低的事务可能不会成为问题,但是在有上千个用户的系统中,将不能忍受这样的事务。
编码指导方针
以下是编写有效事务的指导原则:
不要在事务处理期间要求用户输入。
在事务启动之前,获得所有需要的用户输入。如果在事务处理期间还需要其它的用户输入,则回滚当前的事务,并在提供了用户输入之后重新启动该事务。即使用户 立即响应,作为人,其反应时间也要比计算机慢得多。事务占用的所有资源都要保持很长的时间,这就有可能造成阻塞问题。如果用户没有响应,该事务就会仍保持 活动状态,并锁定关键资源,直到他们响应为止,但是用户可能会几分钟甚至几小时都不响应。
在浏览数据时,尽量不要打开事务。
在所有预备的数据分析完成之前,不应启动事务。
保持事务尽可能地短。
在知道了必须要进行的修改之后,启动事务,执行修改语句,然后立即提交或回滚。只有在需要时,才打开事务。
灵活地使用更低的事务隔离级别。
可以很容易地编写出许多使用授权读事务隔离级别的应用程序。并不是所有的事务都要求可串行事务隔离级别。
灵活地使用更低的游标并发选项,如乐观并发选项。
在很少有可能并发更新的系统中,处理某个偶然的"别人在您读取数据后更改了该数据"错误的开销,要比在读取数据时始终锁定行的开销小得多。
在事务中尽量使访问的数据量最小。
这样可以减少锁定的行数,从而减少事务之间的争夺。
避免并发问题
为了防止并发问题,应该小心地管理隐性事务。在使用隐性事务时,COMMIT 或 ROLLBACK 之后的下一个 Transact-SQL 语句会自动启动一个新事务。这可能在应用程序浏览数据时,甚至在要求用户输入时,打开新的事务。在完成保护数据修改所需要的最后一个事务之后和再次需要一 个事务来保护数据修改之前,关闭隐性事务。该进程使 Microsoft® SQL Server™ 得以在应用程序正在浏览数据并获取来自用户的输入时使用自动提交模式。
可以监视个别用户活动,以查明可能正阻塞其它事务的事务,或导致 Microsoft® SQL Server™ 的性能比预期的低的事务。
监视用户活动有助于识别趋势如某些用户运行的事务类型,执行低效率的特殊查询的数量,以及占用资源最多的事务类型。
若要收集有关用户的统计信息,请使用 SQL 事件探查器或系统监视器(Windows NT® 4.0 中的 Windows NT 性能监视器)。使用 SQL Server 企业管理器"当前活动"窗口对 SQL Server 执行特殊监视,这使您得以确定系统上的用户活动。
说明 一些需要锁保护的操作可能不明显,例如系统目录表和索引上的锁。
大多数阻塞问题的发生是因为一个进程控制锁的时间过长,导致阻塞的进程链都在其它进程上等待锁。
常见的阻塞情形包括:
提交执行时间长的查询。
长时间运行的查询会阻塞其它查询。例如,影响很多行的 DELETE 或 UPDATE 操作能获取很多锁,这些锁不论是否升级到表锁都阻塞其它查询。因此,一般不要将长时间运行的决策支持查询和联机事务处理 (OLTP) 查询混在一起。解决方案是想办法优化查询,如更改索引、将大的复杂查询分成简单的查询或在空闲时间或单独的计算机上运行查询。
查询运行时间长并由此导致阻塞的一个原因是这些查询不适当地使用游标。游标可能是在结果集中浏览的便利方法,但使用游标可能比使用面向集合的查询慢。
取消没有提交或回滚的查询。
如果应用程序取消查询(如使用开放式数据库连接 (ODBC) sqlcancel 函数)但没有同时发出所需数目的 ROLLBACK 和 COMMIT 语句,则会发生这种情况。取消查询并不自动回滚或提交事务。取消查询后,所有在事务内获取的锁都将保留。应用程序必须提交或回滚已取消的事务,从而正确地 管理事务嵌套级。
应用程序没处理完所有结果。
将查询发送到服务器后,所有应用程序必须立即完成提取所有结果行。如果应用程序没有提取所有结果行,锁可能会留在表上而阻塞其他用户。如果使用的应用程序 将 Transact-SQL 语句透明地提交给服务器,则该应用程序必须提取所有结果行。如果应用程序没这样做(如果无法配置它执行此操作),则可能无法解决阻塞问题。为避免此问题, 可以将这些应用程序限制在报表或决策支持数据库上。
分布式客户端/服务器死锁。
与常规死锁不同,分布式死锁无法由 Microsoft® SQL Server™ 2000 自动检测到。如果应用程序打开多个与 SQL Server 的连接并异步提交查询,则可能会发生分布式客户端/服务器死锁。
例如,一个客户端应用程序线程有两个开放式连接。该线程异步启动事务并在第一个连接上发出查询。应用程序随后启动其它事务,在另一个连接上发出查询并等待 结果。当 SQL Server 返回其中一个连接的结果时,应用程序开始处理这些结果。应用程序就这样处理结果,直到生成结果的查询被另一个连接上执行的查询阻塞而导致再没有可用的结果 为止。此时第一个连接阻塞,无限期等待处理更多的结果。第二个连接没有在锁上阻塞,但仍试图将结果返回给应用程序。然而,由于应用程序阻塞而在第一个连接 上等待结果,第二个连接的结果将得不到处理。
若要避免此问题,请执行下列任一操作:
对每个查询使用查询超时。
对每个查询使用锁定超时。有关更多信息,请参见自定义锁超时。
使用绑定连接。有关更多信息,请参见使用绑定连接。
SQL Server 本质上是受客户端应用程序操纵的傀儡。客户端应用程序对服务器上获取的锁几乎有完全的控制(并对锁负责)。虽然 SQL Server 锁管理器自动使用锁保护事务,但这受客户端应用程序发出的查询类型和对结果的处理方式的直接鼓动。因此,大多数阻塞问题的解决方案都涉及检查客户端应用程 序。
阻塞问题常要求检查应用程序提交的 SQL 语句本身,以及检查与连接管理、所有结果行的处理等有关的应用程序行为本身。如果开发工具不允许显式控制连接管理、查询超时、结果处理等,阻塞问题可能得不到解决。
设计应用程序以避免阻塞的准则包括:
不要使用或设计使用户得以填写编辑框的应用程序,编辑框会生成长时间运行的查询。例如,不要使用或设计提示用户输入的应用程序,允许某些字段保留空白或允许输入通配符。这可能导致应用程序提交运行时间过长的查询,从而导致阻塞问题。
不要使用或设计使用户得以在事务内输入内容的应用程序。
允许取消查询。
使用查询或锁定超时,防止失控查询和避免分布式死锁。
立即完成提取所有结果行。
使事务尽可能简短。
显式控制连接管理。
在所预计的并发用户全负荷下对应用程序进行应力测试。
2.死锁
当某组资源的两个或多个线程之间有循环相关性时,将发生死锁。
死锁是一种可能发生在任何多线程系统中的状态,而不仅仅发生在关系数据库管理系统中。多线程系统中的一个线程可能获取一个或多个资源(如锁)。如果正获取 的资源当前为另一线程所拥有,则第一个线程可能必须等待拥有线程释放目标资源。这时就说等待线程在那个特定资源上与拥有线程有相关性。
如果拥有线程需要获取另外一个资源,而该资源当前为等待线程所拥有,则这种情形将成为死锁:在事务提交或回滚之前两个线程都不能释放资源,而且它们因为正 等待对方拥有的资源而不能提交或回滚事务。例如,运行事务 1 的线程 T1 具有 Supplier 表上的排它锁。运行事务 2 的线程 T2 具有 Part 表上的排它锁,并且之后需要 Supplier 表上的锁。事务 2 无法获得这一锁,因为事务 1 已拥有它。事务 2 被阻塞,等待事务 1。然后,事务 1 需要 Part 表的锁,但无法获得锁,因为事务 2 将它锁定了。事务在提交或回滚之前不能释放持有的锁。因为事务需要对方控制的锁才能继续操作,所以它们不能提交或回滚。
说明 死锁经常与正常阻塞混淆。当一个事务锁定了另一个事务需要的资源,第二个事务等待锁被释放。默认情况下,SQL Server 事务不会超时(除非设置了 LOCK_TIMEOUT)。第二个事务被阻塞,而不是被死锁。
阻塞是死锁的前兆,非正常阻塞是引起死锁的原因.
编写有效的事务
尽可能使事务保持简短很重要。启动事务后,DBMS 必须将很多资源控制到事务结束时,以保护事务的 ACID 属性。如果修改数据,则必须用排它锁保护修改过的行,以防止任何其它事务读取该行,并且必须将排它锁控制到提交或回滚事务时为止。根据事务隔离级别设置, SELECT 语句可以获取必须被控制到提交或回滚事务时为止的锁。特别是在有很多用户的系统中,必须尽可能使事务保持简短以减少并发连接间的资源锁定争夺。在有少量用 户的系统中,运行时间长、效率低的事务可能不会成为问题,但是在有上千个用户的系统中,将不能忍受这样的事务。
编码指导方针
以下是编写有效事务的指导原则:
不要在事务处理期间要求用户输入。
在事务启动之前,获得所有需要的用户输入。如果在事务处理期间还需要其它的用户输入,则回滚当前的事务,并在提供了用户输入之后重新启动该事务。即使用户 立即响应,作为人,其反应时间也要比计算机慢得多。事务占用的所有资源都要保持很长的时间,这就有可能造成阻塞问题。如果用户没有响应,该事务就会仍保持 活动状态,并锁定关键资源,直到他们响应为止,但是用户可能会几分钟甚至几小时都不响应。
在浏览数据时,尽量不要打开事务。
在所有预备的数据分析完成之前,不应启动事务。
保持事务尽可能地短。
在知道了必须要进行的修改之后,启动事务,执行修改语句,然后立即提交或回滚。只有在需要时,才打开事务。
灵活地使用更低的事务隔离级别。
可以很容易地编写出许多使用授权读事务隔离级别的应用程序。并不是所有的事务都要求可串行事务隔离级别。
灵活地使用更低的游标并发选项,如乐观并发选项。
在很少有可能并发更新的系统中,处理某个偶然的"别人在您读取数据后更改了该数据"错误的开销,要比在读取数据时始终锁定行的开销小得多。
在事务中尽量使访问的数据量最小。
这样可以减少锁定的行数,从而减少事务之间的争夺。
避免并发问题
为了防止并发问题,应该小心地管理隐性事务。在使用隐性事务时,COMMIT 或 ROLLBACK 之后的下一个 Transact-SQL 语句会自动启动一个新事务。这可能在应用程序浏览数据时,甚至在要求用户输入时,打开新的事务。在完成保护数据修改所需要的最后一个事务之后和再次需要一 个事务来保护数据修改之前,关闭隐性事务。该进程使 Microsoft® SQL Server™ 得以在应用程序正在浏览数据并获取来自用户的输入时使用自动提交模式。
可以监视个别用户活动,以查明可能正阻塞其它事务的事务,或导致 Microsoft® SQL Server™ 的性能比预期的低的事务。
监视用户活动有助于识别趋势如某些用户运行的事务类型,执行低效率的特殊查询的数量,以及占用资源最多的事务类型。
若要收集有关用户的统计信息,请使用 SQL 事件探查器或系统监视器(Windows NT® 4.0 中的 Windows NT 性能监视器)。使用 SQL Server 企业管理器"当前活动"窗口对 SQL Server 执行特殊监视,这使您得以确定系统上的用户活动。