我有两个不同的表,分别称为Processing (30M records for now)
和EtlRecord (4.3M records for now)
。
就像表名所暗示的那样,这些表将用于通过ETL进行数据标准化。
我们正在尝试处理具有批处理的记录,每个批处理中有1000条记录。
SELECT TOP 1000 P.StreamGuid
FROM [staging].[Processing] P (NOLOCK)
LEFT JOIN [core].[EtlRecord] E (NOLOCK) ON E.StreamGuid = P.StreamGuid
WHERE E.StreamGuid IS NULL
AND P.CompleteDate IS NOT NULL
AND P.StreamGuid IS NOT NULL
执行此查询大约需要20秒钟。而且,我们希望有越来越多的数据,尤其是在EtlRecord表中。为了提高此查询的性能,我检查了下面共享的实际执行计划。
如您所见,最耗时的部分是索引查找以确定EtlRecord表中的空记录。我尝试了一些更改,但无法对其进行改进。
附加说明
Processing
表中有8列是布尔标志,EtlRecord
表中有4列。任何改进此查询的建议都会很有帮助。
答案 0 :(得分:1)
好吧,在您的查询中,您需要从[staging].[Processing]
获取记录,而[core].[EtlRecord]
中没有相应的记录。
您可以先删除继续的记录。
DELETE [staging].[Processing]
FROM [staging].[Processing] P
INNER JOIN [core].[EtlRecord] E
ON E.StreamGuid = P.StreamGuid;
如果需要,可以对批次使用删除。删除这些记录将简化我们的初始查询,并简化uniqueidentifier
的联接。您只需要对每个批次执行以下操作:
SELECT TOP 1000 StreamGuid
INTO #buffer
FROM [staging].[Processing]
WHERE CompleteDate IS NOT NULL
AND StreamGuid IS NOT NULL;
-- do whatevery you need with this records
DELETE FROM [staging].[Processing]
WHERE StreamGuid IN (SELECT StreamGuid FROM #buffer);
此外,您已经说过已经创建了所有索引,但是执行计划建议的索引并不总是最好的。这部分:
WHERE CompleteDate IS NOT NULL
AND StreamGuid IS NOT NULL;
似乎是filtered index的很好的候选者,尤其是对于其中的一列,如果大量行具有NULL
值的话。
答案 1 :(得分:1)
首先,DDL和易于使用的示例数据(如下所示)将有很大帮助。您可以复制/粘贴我的解决方案,然后在本地运行它们,以查看我在说什么。
IF OBJECT_ID('tempdb..#processing','U') IS NOT NULL DROP TABLE #processing;
IF OBJECT_ID('tempdb..#EtlRecord','U') IS NOT NULL DROP TABLE #EtlRecord;
SELECT TOP (100)
StreamGuid = NEWID(),
CompleteDate = CASE WHEN CHECKSUM(NEWID())%3 < 2 THEN GETDATE() END
INTO #processing
FROM sys.all_columns AS a
SELECT TOP (80) p.StreamGuid
INTO #EtlRecord
FROM #Processing AS p;
ALTER TABLE #processing ALTER COLUMN StreamGuid UNIQUEIDENTIFIER NOT NULL;
ALTER TABLE #EtlRecord ALTER COLUMN StreamGuid UNIQUEIDENTIFIER NOT NULL;
GO
ALTER TABLE #processing ADD CONSTRAINT pk_processing PRIMARY KEY CLUSTERED(StreamGuid);
ALTER TABLE #etlRecord ADD CONSTRAINT pk_etlRecord PRIMARY KEY CLUSTERED(StreamGuid);
GO
接下来了解到,如果没有ORDER BY子句,则不能保证您的查询每次都返回相同的记录。例如,如果SQL Server选择并行执行计划,则您肯定会获得不同的行。我还看到了包含ORDER BY会真正提高性能的情况。
记住这一点,而不是这个...
SELECT --TOP 1000
P.StreamGuid
FROM #processing AS p
LEFT JOIN #etlRecord AS e ON e.StreamGuid = p.StreamGuid
WHERE e.StreamGuid IS NOT NULL
AND P.CompleteDate IS NOT NULL
...将返回与此完全相同的东西:
SELECT TOP 1000
P.StreamGuid
FROM #processing AS p
JOIN #etlRecord AS e ON e.StreamGuid = p.StreamGuid
WHERE p.CompleteDate IS NOT NULL;
请注意,WHERE e.StreamGuid = p.StreamGuid
已经暗示两个值均为 NOT NULL 。请注意,此查询...
DECLARE @X INT;
SELECT AreTheyEqual = IIF(@X=@X,'Yep','Nope');
...返回:
AreTheyEqual
------------
Nope
我同意关于过滤索引发布的解决方案@gotqn。使用我的样本数据,您可以添加如下内容:
CREATE NONCLUSTERED INDEX nc_processing ON #processing(CompleteDate,StreamGuid)
WHERE CompleteDate IS NOT NULL;
然后,您可以在查询中添加ORDER BY CompleteDate
,以迫使优化器选择该索引(在我的系统上,除非添加ORDER BY,否则它不会选择该索引)。 ORDER BY将使您查询确定性和更可预测。
答案 2 :(得分:0)
我建议这样写:
SELECT TOP 1000 P.StreamGuid
FROM [staging].[Processing] P
WHERE P.CompleteDate IS NOT NULL AND
P.StreamGuid IS NOT NULL AND
NOT EXISTS (SELECT 1
FROM [core].[EtlRecord] E
WHERE E.StreamGuid = P.StreamGuid
);
我删除了NOLOCK
指令。仅当您真正知道自己在做什么时才使用它-并且准备读取无效数据。
那么您肯定要在EtlRecord(StreamGuid)
上建立索引。
您可能还希望在Processing(CompleteDate, StreamGuid)
上建立索引。这至少是查询的覆盖索引。