使用外键上的显式单键值克服MERGE JOIN(INDEX SCAN)


9

已添加7/11问题是在MERGE JOIN期间由于索引扫描而发生死锁。在这种情况下,一个事务试图在FK父表的整个索引上获得S锁,但是以前另一个事务将X锁置于索引的键值上。

让我从一个小例子开始(使用70-461 cource的TSQL2012 DB):

CREATE TABLE [Sales].[Orders](
[orderid] [int] IDENTITY(1,1) NOT NULL,
[custid] [int] NULL,
[empid] [int] NOT NULL,
[shipperid] [int] NOT NULL,
... )

[custid], [empid], [shipperid]是相应的相关参数[Sales].[Customers], [HR].[Employees], [Sales].[Shippers]。在每种情况下,我们在父母表中的引用列上都有一个聚集索引。

ALTER TABLE [Sales].[Orders]  WITH CHECK ADD  CONSTRAINT [FK_Orders_Customers] FOREIGN KEY([custid]) REFERENCES [Sales].[Customers] ([custid])
ALTER TABLE [Sales].[Orders]  WITH CHECK ADD  CONSTRAINT [FK_Orders_Employees] FOREIGN KEY([empid]) REFERENCES [HR].[Employees] ([empid])
ALTER TABLE [Sales].[Orders]  WITH CHECK ADD  CONSTRAINT [FK_Orders_Shippers] FOREIGN KEY([shipperid])REFERENCES [Sales].[Shippers] ([shipperid])

我正在尝试INSERT [Sales].[Orders] SELECT ... FROM另一个表[Sales].[OrdersCache],该表与[Sales].[Orders]除外键外的结构相同。提及表可能很重要的[Sales].[OrdersCache]一点是聚集索引。

CREATE CLUSTERED INDEX idx_c_OrdersCache ON Sales.OrdersCache ( custid, empid )

正如我期望的那样,当我尝试插入少量数据时,LOOP JOIN可以很好地对外键进行索引查找。

查询数据优化器使用MERGE JOIN来处理大量数据,这是在查询中维护foregn键的最有效方法。

在外例中,使用OPTION(LOOP JOIN)或在显式JOIN情况下使用INNER LOOP JOIN与它无关。

以下是我要在我的环境中运行的查询:

INSERT Sales.Orders (
        custid, empid, shipperid, ... )
SELECT  custid, empid, 2, ...
FROM Sales.OrdersCache

查看该计划,我们可以看到所有3个前键均已通过MERGE JOIN验证。这对我来说不是一种合适的方法,因为它使用带有整个索引锁定的INDEX SCAN。 在外键验证期间合并联接

使用OPTION(LOOP JOIN)是不合适的,因为它的成本比MERGE JOIN高出将近15%(我认为随着数据量的增长,回归会更大)。

在SELECT语句中,您可以看到shipperid整个插入集的attribute的单个值。我认为必须至少有一种方法可以使插入集的验证阶段至少对于不可变属性更快。就像是:

  • 如果我们有用于JOIN验证的未定义子集,则进行LOOP JOIN,MERGE JOIN,HASH JOIN
  • 如果validated列只有一个显式值,则仅进行一次验证(INDEX SEEK)。

是否存在使用代码结构,其他DDL对象等来超越上述情况的通用模式?

添加20/07。解。查询优化器已经通过使用MERGE JOIN进行了“单键-外键”验证优化。并且仅针对Sales.Shippers表,同时将LOOP JOIN用于另一个联接。由于我的父表中有几行,因此Query Optimizer使用了Sort-merge连接算法,并将内部表中的每一行与父表进行一次比较。因此,这就是我的问题的答案,即在单键验证期间是否有任何特定机制可以有效地处理集合中的单个值。那不是一个完美的决定,而是SQL Server优化案例的方式。

性能影响调查显示,在我的案例中,MERGE JOIN和LOOP JOIN插入语句变得大约等于750个同时插入的行,而MERGE JOIN具有以下优势(在CPU时间资源中)。因此,使用OPTION(LOOP JOIN)是适合我的业务流程的解决方案。

Answers:


8

不适合使用OPTION(LOOP JOIN),因为它的费用比MERGE JOIN高出近15%

即使在执行后(实际)计划中,showplan输出中显示的成本百分比也始终是优化器模型估算值。这些成本可能无法反映特定硬件上的实际运行时性能。唯一可以确定的方法是使用您的工作负载测试替代方案,测量对您最重要的指标(经过时间,CPU使用率等)。

以我的经验,优化器从循环联接切换到合并联接以进行外键验证还为时过早。除了最大的操作,我发现循环连接是更好的选择。所谓“大”,是指至少数千万行,当然不是您在问题注释中指出的约一千行。

我认为必须至少有一种方法可以使插入集的验证阶段至少对于不可变属性更快。

从原则上讲,这是有道理的,但如今,在计划构建逻辑中没有外键验证使用的逻辑。当前的逻辑故意是非常通用的,并且与更广泛的查询正交。特定的优化使测试复杂化,并增加了边缘错误的可能性。

是否存在使用代码结构,其他DDL对象等来超越上述情况的通用模式?

不是我知道的。合并联接外键验证的死锁风险是众所周知的,使用最广泛的解决方法是OPTION (LOOP JOIN)提示。无法避免在外键验证期间使用共享锁,因为即使在行版本隔离级别下,这些锁也是正确性必需的

如果要保留多个并发进程以事务方式向父表和子表添加行的功能,没有比循环联接提示更好的一般答案了。但是,如果您愿意序列化数据修改(而不是读取),使用sp_getapplock是一种可靠且简单的技术。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.