Clustered Columnstore 索引中发生死锁

Deadlock occuring in Clustered Columnstore index

我们在交易中使用聚集列存储索引 table 持有订单履行。这个 table 由不同的会话定期更新。但是,每个会话都是特定于订单作业编号的,因此,他们不会尝试同时更新同一行。但是,由于会话之间的以下情况,我们面临死锁问题。

这不是特定于存储过程的。这是由于多个存储过程更新此 table,作为订单履行的一部分,一个接一个地顺序更新。

table 的示例模式非常简单:

CREATE TABLE OrderFulfillments
(
    OrderJobNumber           INT NOT NULL,
    FulfilledIndividualID    BIGINT NOT NULL,
    IsIndividualSuppressed   BIT NOT NULL,
    SuppressionReason        VARCHAR(100) NULL
)

我已经给出了示例死锁图供您参考。请让我知道,我可以采取什么方法来避免这种僵局。我们需要在此 table 中使用聚簇列存储索引,因为我们正在执行聚合操作以查看一个 Individual 已经完成了多少次。没有列存储索引,它可能会更慢。

您假设 NOLOCK 等同于无锁定...这是不正确的。

NOLOCK 等同于 READUNCOMMITTED。

• READUNCOMMITTED and NOLOCK hints apply only to data locks.

All queries, including those with READUNCOMMITTED and NOLOCK hints, acquire Sch-S (schema stability) locks during compilation and execution. Because of this, queries are blocked when a concurrent transaction holds a Sch-M (schema modification) lock on the table.

For example, a data definition language (DDL) operation acquires a Sch-M lock before it modifies the schema information of the table.

Any concurrent queries, including those running with READUNCOMMITTED or NOLOCK hints, are blocked when attempting to acquire a Sch-S lock. Conversely, a query holding a Sch-S lock blocks a concurrent transaction that attempts to acquire a Sch-M lock.

READUNCOMMITTED and NOLOCK cannot be specified for tables modified by insert, update, or delete operations. The SQL Server query optimizer ignores the READUNCOMMITTED and NOLOCK hints in the FROM clause that apply to the target table of an UPDATE or DELETE statement.

You can minimize locking contention while protecting transactions from dirty reads of uncommitted data modifications by using either of the following:

• The READ COMMITTED isolation level with the READ_COMMITTED_SNAPSHOT database option set ON.

• The SNAPSHOT isolation level. For more information about isolation levels, see SET TRANSACTION ISOLATION LEVEL (Transact-SQL).

https://docs.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table

  • 了解您的索引结构可能会导致阻塞,如果说 select 语句需要您的 UPDATE 同时修改的整个页面。

  • 在测试时限制变量。

  • 考虑将 DML 分成多个部分。您可能会找到执行并发修改 table 数据的最佳范围。

在我的例子中,死锁情况是由于锁升级的发生,因为一些实现非常大,在 10,000 或 100k 范围内,它导致锁升级发生在行组级别,在某些情况下, 页面级别。

我解决了这个问题,方法是在交易的最开始有一个临时 table 并在临时 table 上进行更新,最后插入临时 table 相关的履行信息到这个OrderFulfillments。这个 OrderFulfillments 也被临时 table 用来查看个人已经完成了多少次。但是,它是顶部的共享锁而不是独占锁。

通过临时 table,每个会话都在处理自己的副本,并发问题得到解决。