我正在尝试跟踪是否需要更新某些表。我有一个事件表,我想要监视更改,并有另一个表,称为DictionaryRefresh,它跟踪对该表所做的更改。如果事件表被编辑,它的编辑时间将被保存并且晚于DictionaryRefresh表的最后刷新时间,因此表明需要刷新。此外,如果将新行添加到Events表中,则还需要在DictionaryRefresh表中关联新条目 - 因此LEFT JOIN。
以下是表格结构
CREATE TABLE [dbo].[DictionaryRefresh]
(
[LookupKey] [varchar](31) NOT NULL,
[LookupValue] [varchar](255) NOT NULL,
[RecordNumber] [int] NULL,
[RefreshTime] [datetime] NULL,
[EventKey] [varchar](31) NULL,
[MappedLookupKey] [varchar](31) NULL
) ON [PRIMARY]
索引是(在DBEngine Tuning Advisor之后)
CREATE NONCLUSTERED INDEX [idx_DictionaryRefresh2146B4EB]
ON [dbo].[Ifx_DictionaryRefresh] ([LookupKey] ASC)
CREATE NONCLUSTERED INDEX [idx_DictionaryRefresh51EC6492]
ON [dbo].[Ifx_DictionaryRefresh] ([MappedLookupKey] ASC, [RefreshTime] ASC, [RecordNumber] ASC, [EventKey] ASC)
CREATE NONCLUSTERED INDEX [idx_DictionaryRefreshFCDAD7FA]
ON [dbo].[Ifx_DictionaryRefresh] ([LookupValue] ASC)
“事件”表格如下:
CREATE TABLE [dbo].[Events](
[RecordNumber] [int] NOT NULL,
...
[EventKey] [varchar](31) NOT NULL,
...
[EditTime] [datetime] NULL,
...
PRIMARY KEY CLUSTERED([RecordNumber] ASC)
CREATE NONCLUSTERED INDEX [idxEvents299ADAC8]
ON [dbo].[Events]([EditTime] ASC)
CREATE NONCLUSTERED INDEX [idxEvents5B151A5E]
ON [dbo].[Events]([EventKey] ASC)
现在我正在运行的SQL如下 - 返回几乎需要一分钟。如果我执行子查询,它只会立即返回。
IF EXISTS (
SELECT 1
FROM (
SELECT
e.EventKey AS DictionaryKey
,ISNULL(e.EditTime, '1 Jan 1900 01:00') AS EditTime
,e.RecordNumber AS DictionaryRecordNumber
FROM Events e) d
LEFT JOIN DictionaryRefresh r ON r.RecordNumber = DictionaryRecordNumber
AND r.EventKey = DictionaryKey
AND r.MappedLookupKey = 'M18E2I501'
WHERE r.RefreshTime < d.EditTime
OR r.RecordNumber IS NULL)
BEGIN
PRINT 'TRUE'
END
DictionaryRefresh
表中约有130K行,Events
表中约有8K行
当DictionaryRefresh
表为空或小时,它非常快,但随着DictionaryRefresh中的行数增加而减慢,特别是如果没有符合条件的话。
这是执行计划。
和突出的统计数据(索引寻求占成本的94% - 访问的行数实际上是事件表中行数的平方)...
我尝试过更换
IF EXISTS
带
IF (SELECT COUNT ...) <> 0
以及
IF (SELECT TOP 1 1 ...) = 1
但似乎没有更快。
我很明白你可能有任何建议。
提前致谢。
取值
答案 0 :(得分:2)
稍微重新格式化您的查询我来到这里:
IF EXISTS ( SELECT 1
FROM (SELECT e.EventKey AS DictionaryKey
,ISNULL(e.EditTime, '1 Jan 1900 01:00') AS EditTime
,e.RecordNumber AS DictionaryRecordNumber
FROM Events e) d
LEFT OUTER JOIN DictionaryRefresh r
ON r.RecordNumber = d.DictionaryRecordNumber
AND r.EventKey = d.DictionaryKey
AND r.MappedLookupKey = 'M18E2I501'
WHERE r.RefreshTime < d.EditTime
OR r.RecordNumber IS NULL)
BEGIN
PRINT 'TRUE'
END
我没有在Events
上看到子查询的充分理由,因此等效查询会变成这样:
IF EXISTS ( SELECT *
FROM Events e
LEFT OUTER JOIN DictionaryRefresh r
ON r.RecordNumber = e.RecordNumber
AND r.EventKey = e.EventKey
AND r.MappedLookupKey = 'M18E2I501'
WHERE r.RefreshTime < ISNULL(e.EditTime, '1 Jan 1900 01:00')
OR r.RecordNumber IS NULL
)
BEGIN
PRINT 'TRUE'
END
首先要注意的是,您在r.RefreshTime
子句中使用WHERE
。由于<
运算符仅在左侧为DEFINED并且小于右侧时才返回true,这意味着每次r.RefreshTime
为NULL时,将跳过记录。然而,下一行你明显提到你想要r.RecordNumber
为NULL的所有记录,这些记录只能在值实际为NULL时发生,或者当LEFT OUTER JOIN
找不到匹配时才会发生。所以这里有一点冲突。您想要INNER JOIN
,或者您真的想要OUTER JOIN
但是需要将r.RefreshTime < d.EditTime
移到JOIN ON
子句。
现在,看看你的表定义,我认为还有一些改进空间。按照您在上面给出的解释Events
表是所有数据的“来源”。它会随着时间的推移而附加,然后偶尔会运行一个扫描“新”和“更新”记录的进程,做一些魔术,然后将DictionorayRefresh(UPDATE
现有记录更新为新{{1}和RefreshTime
新的
[DBO]。[事件]
[DBO]。[DictionaryRefresh]
INSERT
能够为空?不应该一直填写,否则记录的目的是什么?RecordNumber
表的字段上放置FOREIGN KEY
,这样服务器就知道所有值都来自那里Events
也被定义为NULL-able,我认为你希望它总是被填写。否则记录是如何进入表中的?RefreshTime
,但这对此并不重要。无论如何,回到查询。您要弄清楚的是,MappedLookupKey
中的记录是否在Events
中对于给定的DictionaryRefresh
和更新的MappedLookupKey
具有匹配的记录而不是相应的EditTime
}。或者,根本没有这样的记录(对于这个RefreshTime
)
我个人会这样写:
MappedLookupKey
为了使这项工作更快,您需要以下索引:
IF EXISTS ( SELECT *
FROM Events e
WHERE NOT EXISTS ( SELECT *
FROM DictionaryRefresh r
WHERE r.RecordNumber = e.RecordNumber
AND r.EventKey = e.EventKey
AND r.MappedLookupKey = 'M18E2I501'
AND r.RefreshTime >= e.EditTime )
)
BEGIN
PRINT 'TRUE'
END
在CREATE INDEX idx1 ON DictionaryRefresh (MappedLookupKey, RecordNumber, EventKey, RefreshTime)
表格中,我认为Events
会...
有趣的事实:您的PK
同时使用JOIN
和RecordNumber
(同样是一个无法使用的字段,可能没有充分的理由)。但是,我们已经知道EventKey
在[事件]中唯一标识了一条记录(它是RecordNumber
!),所以如果你只是PK
加入实际应该做的事情,除非你能拥有RecordNumber
中不同的EventKey
值?这对我来说没有意义...事实上,DictonaryRefresh
中似乎并不真正需要该字段,因为它首先可以在DictionaryRefresh
中找到。如果这个假设是正确的,你可以将它从表中移除,从而Events
再次加快速度。
有点长时间阅读,希望我没有做太多的事情=)
答案 1 :(得分:1)
CREATE NONCLUSTERED INDEX ix1
ON dbo.DictionaryRefresh (RecordNumber, EventKey, MappedLookupKey, RefreshTime)
CREATE NONCLUSTERED INDEX ix2
ON dbo.[Events] (RecordNumber, EventKey, EditTime)
IF EXISTS (
SELECT TOP(1) 1
FROM dbo.[Events] e /*WITH(INDEX(ix2))*/
LEFT JOIN dbo.DictionaryRefresh r /*WITH(INDEX(ix1))*/ ON r.RecordNumber = e.RecordNumber
AND r.EventKey = e.EventKey
AND r.MappedLookupKey = 'M18E2I501'
WHERE (r.RefreshTime < e.EditTime AND e.EditTime IS NOT NULL)
OR r.RecordNumber IS NULL
)
BEGIN
PRINT 'TRUE'
END