Bob*_*bst 6 performance sql-server sql-server-2016 except query-performance
我们有一些顾问致力于扩展内部数据仓库。我正在做代码审查并在所有加载过程中遇到这种模式:
MERGE [EDHub].[Customer].[Class] AS TARGET
USING (
SELECT <columns>
FROM [dbo].[vw_CustomerClass]
WHERE JHAPostingDate = @PostingDate
) AS SOURCE
ON TARGET.BankId = SOURCE.BankId -- This join is on the business keys
AND TARGET.Code = SOURCE.Code
WHEN NOT MATCHED BY TARGET
THEN
<INSERT Statement>
WHEN MATCHED
AND TARGET.IsLatest = 1
AND EXISTS (
SELECT SOURCE.[HASH]
EXCEPT
SELECT TARGET.[Hash]
)
THEN
<UPDATE Statement>
Run Code Online (Sandbox Code Playgroud)
要点是,如果我们有一个新的业务键,则插入,但如果业务键存在并且属性的散列与我们的当前行不匹配,则更新旧行并插入一个新行(稍后在代码中)。一切正常,但是当我看到这段代码时我暂停了
AND EXISTS (
SELECT SOURCE.[HASH]
EXCEPT
SELECT TARGET.[Hash]
)
Run Code Online (Sandbox Code Playgroud)
与 SOURCE.[HASH] <> TARGET.[Hash] 相比,它似乎过于复杂。EXCEPT 将进行准确的 NULL 比较,但在我们的情况下,哈希值永远不会为 NULL(或者我们有更大的问题)。我希望我们的代码易于阅读,这样当有人必须维护它时,它不会混淆。我向我们的顾问询问了它,他们推测它可能会因为集合操作而更快,但我决定编写一个简单的测试(下面的测试代码)。
我注意到的第一件事是 EXISTS/EXCEPT 有一个更复杂的查询计划,但这并不总是很糟糕
我运行了每个选择的客户端统计信息,<> 连接产生了 12,000 与 EXISTS/EXCEPT 的 25,000 的总执行时间。我想将此提交给我们的顾问,并要求重构该语句,但希望在此处获得反馈:
测试脚本:
CREATE TABLE r (hash VARBINARY(8000))
CREATE TABLE l (hash VARBINARY(8000))
SET NOCOUNT ON
DECLARE @x INT = 10000
WHILE @x <> 0 BEGIN
INSERT INTO dbo.r ( hash ) SELECT HASHBYTES('SHA2_256',CAST(NEWID() AS VARCHAR(200)))
INSERT INTO dbo.l ( hash ) SELECT HASHBYTES('SHA2_256',CAST(NEWID() AS VARCHAR(200)))
SET @x = @x-1
END
INSERT INTO dbo.r ( hash ) VALUES ( NULL )
INSERT INTO dbo.l ( hash ) VALUES ( NULL )
SELECT COUNT(1)
FROM dbo.l
CROSS JOIN dbo.r
WHERE ISNULL(r.hash,0) <> ISNULL(l.hash,0)
SELECT COUNT(1)
FROM dbo.l
CROSS JOIN dbo.r
WHERE EXISTS(SELECT r.hash except select l.HASH)
Run Code Online (Sandbox Code Playgroud)
我不喜欢ISNULL哨兵值,它需要选择现在或以后永远不会合法出现在数据中的值,我个人发现包含这些的表达式更难以推理。
对于您的测试设备,我尝试了四种不同的方式来表达查询并得到了所述的结果。
SELECT COUNT(1)
FROM dbo.l
CROSS JOIN dbo.r
WHERE r.hash <> l.hash
OR ( r.hash IS NULL
AND l.hash IS NOT NULL )
OR ( l.hash IS NULL
AND r.hash IS NOT NULL )
Run Code Online (Sandbox Code Playgroud)
SQL Server 执行时间:CPU 时间 = 30968 毫秒,已用时间 = 8230 毫秒。
SELECT COUNT(1)
FROM dbo.l
CROSS JOIN dbo.r
WHERE ISNULL(r.hash, 0) <> ISNULL(l.hash, 0)
Run Code Online (Sandbox Code Playgroud)
SQL Server 执行时间:CPU 时间 = 31594 毫秒,已用时间 = 9230 毫秒。
SELECT COUNT(1)
FROM dbo.l
CROSS JOIN dbo.r
WHERE EXISTS(SELECT r.hash
EXCEPT
SELECT l.HASH)
Run Code Online (Sandbox Code Playgroud)
SQL Server 执行时间:CPU 时间 = 46531 毫秒,已用时间 = 13191 毫秒。
SELECT COUNT(1)
FROM dbo.l
CROSS JOIN dbo.r
WHERE NOT EXISTS(SELECT r.hash
INTERSECT
SELECT l.HASH)
Run Code Online (Sandbox Code Playgroud)
SQL Server 执行时间:CPU 时间 = 23812 毫秒,已用时间 = 6760 毫秒。
因此,在此基础上,最后一个将是明显的赢家 - 以及带有指向未记录查询计划的链接的代码注释:对于不熟悉该模式的任何人的平等比较。
但是您也应该测试这种模式是否可以在您的实际MERGE查询中重现。