我有如下查询:
DELETE FROM tblFEStatsBrowsers WHERE BrowserID NOT IN (
SELECT DISTINCT BrowserID FROM tblFEStatsPaperHits WITH (NOLOCK) WHERE BrowserID IS NOT NULL
)
tblFEStatsBrowsers 有 553 行。
tblFEStatsPaperHits 有 47.974.301 行。
tblFEStats浏览器:
CREATE TABLE [dbo].[tblFEStatsBrowsers](
[BrowserID] [smallint] IDENTITY(1,1) NOT NULL,
[Browser] [varchar](50) NOT NULL,
[Name] [varchar](40) NOT NULL,
[Version] [varchar](10) NOT NULL,
CONSTRAINT [PK_tblFEStatsBrowsers] PRIMARY KEY CLUSTERED ([BrowserID] ASC)
)
tblFEStatsPaperHits:
CREATE TABLE [dbo].[tblFEStatsPaperHits](
[PaperID] [int] NOT NULL,
[Created] [smalldatetime] NOT NULL,
[IP] [binary](4) NULL,
[PlatformID] [tinyint] NULL,
[BrowserID] [smallint] NULL,
[ReferrerID] [int] NULL,
[UserLanguage] [char](2) NULL
)
tblFEStatsPaperHits 上有一个不包含 BrowserID 的聚集索引。因此,执行内部查询需要对 tblFEStatsPaperHits 进行全表扫描——这完全没问题。
目前,对 tblFEStatsBrowsers 中的每一行执行一次完整扫描,这意味着我对 tblFEStatsPaperHits 进行了 553 次全表扫描。
重写为 WHERE EXISTS 不会改变计划:
DELETE FROM tblFEStatsBrowsers WHERE NOT EXISTS (
SELECT * FROM tblFEStatsPaperHits WITH (NOLOCK) WHERE BrowserID = tblFEStatsBrowsers.BrowserID
)
但是,正如 Adam Machanic 所建议的,添加 HASH JOIN 选项确实会产生最佳执行计划(只需扫描一次 tblFEStatsPaperHits):
DELETE FROM tblFEStatsBrowsers WHERE NOT EXISTS (
SELECT * FROM tblFEStatsPaperHits WITH (NOLOCK) WHERE BrowserID = tblFEStatsBrowsers.BrowserID
) OPTION (HASH JOIN)
现在这不是如何解决这个问题的问题 - 我可以使用 OPTION (HASH JOIN) 或手动创建临时表。我更想知道为什么查询优化器会使用它当前执行的计划。
由于 QO 在 BrowserID 列上没有任何统计信息,我猜它假设最差 - 5000 万个不同的值,因此需要相当大的内存/tempdb 工作表。因此,最安全的方法是对 tblFEStatsBrowsers 中的每一行执行扫描。两个表的BrowserID列之间没有外键关系,所以QO不能从tblFEStatsBrowsers中扣除任何信息。
这就是听起来那么简单的原因吗?
更新 1
给出几个统计数据: OPTION (HASH JOIN):
208.711 logical reads (12 scans)
OPTION (LOOP JOIN, HASH GROUP):
11.008.698 逻辑读取 (~scan per BrowserID (339))
没有选项:
11.008.775 逻辑读取(~scan per BrowserID (339))
更新 2
优秀的答案,你们所有人 - 谢谢!很难只挑一个。虽然 Martin 是第一个,而 Remus 提供了一个很好的解决方案,但我必须把它交给 Kiwi,让他们对细节有所了解 :)