【问题标题】:SQL Server SELECT statements causing blockingSQL Server SELECT 语句导致阻塞
【发布时间】:2009-06-19 09:50:09
【问题描述】:

我们正在使用带有大型 select 语句的 SQL Server 2005 数据库(无行版本控制),并且我们看到它阻止了其他语句的运行(使用 sp_who2 看到)。我没有意识到 SELECT 语句可能会导致阻塞 - 我能做些什么来缓解这种情况吗?

【问题讨论】:

  • 您是否会使用更高的隔离级别,例如可重复读取或可序列化?一些 ADO 和 CLR 组件在未经您明确同意的情况下潜入可序列化... (key, range, table) 上的阻塞资源是什么?
  • 我没有指定隔离级别。有没有办法找出正在使用的隔离级别?
  • 是的,请查看 sys.dm_exec_sessions。 transaction_isolation_level 列。
  • 对所涉及的表和数据进行一些描述会很有帮助。如果您可以优化查询,那么锁显然会保留更短的时间。如果它是只读事务,另一种选择是检索您需要对临时表进行操作的数据集,然后针对临时表进行查询。您可以在高隔离级别下填充所有临时表,以保证活动表的事务“快照”。

标签: sql-server performance blocking


【解决方案1】:

SELECT 可以阻止更新。正确设计的数据模型和查询只会导致最小的阻塞并且不会成为问题。 '通常' WITH NOLOCK 提示几乎总是错误的答案。正确的答案是调整您的查询,使其不会扫描大表。

如果查询不可调,则应首先考虑SNAPSHOT ISOLATION level,其次应考虑使用DATABASE SNAPSHOTS,最后一个选项应为DIRTY READS(最好更改isolation level,而不是使用NOLOCK HINT)。请注意,脏读,顾名思义,会返回不一致的数据(例如,您的总工作表可能不平衡)。

【讨论】:

  • 引用了另一个问题:stackoverflow.com/questions/1018651/…
  • 不要误会我的意思,这是一个很好的答案,但我尽量不要在不了解它们的应用和设计的情况下说绝对。例如,如果您的应用程序是具有“查询”功能的企业应用程序怎么办?猜猜谁在写你的查询?到你的数据库?客户!那里没有足够的索引!快照隔离级别?有多少人同时访问您的数据库?因为让我们用这个选项来讨论资源!所以,是的,我们这些不是 DB 大师的未洗群众必须使用脏读!
  • @JustLooking 你的观点很好,但我认为 Remus 的回答是一个很好的指导方针——尤其是。他的第一个论点是要求 nolock 提示整体设计或查询有问题。还有一个使用事务的数据库呢?这不应该表明应该避免 nolock 吗?似乎它会产生许多晦涩难懂且难以测试的错误,因为它们是断断续续的。
  • "'通常的' WITH NOLOCK 提示几乎总是错误的答案。" 如果我​​的问题是:我想做脏的、非阻塞的 SELECT 语句,这些语句当前会导致 20-60 秒的阻塞.那么正确答案是什么?
【解决方案2】:

来自documentation

Shared (S) 锁允许并发事务读取(SELECT) 悲观并发控制下的资源。有关详细信息,请参阅Types of Concurrency Control。当资源上存在shared (S) 锁时,没有其他事务可以修改数据。 Shared (S) 资源上的锁在读取操作完成后立即释放,除非事务隔离级别设置为可重复读取或更高,或者使用锁定提示在事务期间保留 shared (S) 锁。

shared lock 与另一个共享锁或更新锁兼容,但与排他锁不兼容。

这意味着您的 SELECT 查询将阻止 UPDATEINSERT 查询,反之亦然。

SELECT 查询会在从表中读取数据块时放置一个临时共享锁,并在读取完成后将其移除。

在锁定存在期间,您将无法对锁定区域中的数据进行任何操作。

两个SELECT 查询永远不会互相阻塞(除非它们是SELECT FOR UPDATE

您可以在您的数据库上启用SNAPSHOT 隔离级别并使用它,但请注意,它不会阻止UPDATE 查询被SELECT 查询锁定(这似乎是您的情况)。

不过,它会阻止SELECT 查询被UPDATE 锁定。

另请注意,SQL ServerOracle 不同,它使用锁管理器并将其锁定在内存链表中。

这意味着在重负载下,仅仅放置和删除锁可能会很慢,因为链表本身应该被事务线程锁定。

【讨论】:

  • 不要假设有关 SQL Servere 的锁管理器的实现细节。可以说不是链表。
  • 有没有办法绕过 MySQL 中的共享锁?我尝试使用每个可用的隔离级别,但它们都不能阻止更新查询被选择锁定(这是我的情况)。
  • @TravisJ:请将其作为一个单独的问题发布,提供可重现的用例。
【解决方案3】:

要执行脏读,您可以:

 using (new TransactionScope(TransactionScopeOption.Required, 
 new TransactionOptions { 
 IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted }))
 {
 //Your code here
 }

SelectCommand = "SELECT * FROM Table1 WITH (NOLOCK) INNER JOIN Table2 WITH (NOLOCK) ..."

请记住,您必须在要脏读的每个表之后写入 WITH (NOLOCK)

【讨论】:

    【解决方案4】:

    您可以将transaction level 设置为未提交的读取

    【讨论】:

    • 这不是和使用(NOLOCK)一样吗?我真的不想去那个方向......
    • 和NOLOCK一样,只读查询也没问题。
    • 不,不适用于只读查询,因为 NOLOCK 可以应用于只读查询,因此它存在(并且有很多)的任何问题都适用于 '也是只读的。非资产负债表报表仍然是“只读”的,但几乎不正确。
    【解决方案5】:

    您可能还会遇到死锁:

    “只涉及一张表的死锁” http://sqlblog.com/blogs/alexander_kuznetsov/archive/2009/01/01/reproducing-deadlocks-involving-only-one-table.aspx

    和/或不正确的结果:

    “在 READ COMMITTED 和 REPEATABLE READ 下选择可能会返回不正确的结果。”

    http://www2.sqlblog.com/blogs/alexander_kuznetsov/archive/2009/04/10/selects-under-read-committed-and-repeatable-read-may-return-incorrect-results.aspx

    【讨论】:

      【解决方案6】:

      您可以使用WITH(READPAST) 表提示。它与WITH(NOLOCK) 不同。它将在事务开始之前获取数据,并且不会阻止任何人。想象一下,您在事务开始之前运行了语句。

      SELECT * FROM table1  WITH (READPAST)
      

      【讨论】:

        猜你喜欢
        • 2010-11-21
        • 2014-11-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-10-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多