已添加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)是适合我的业务流程的解决方案。