使用 IN() 提高查询性能

San*_*sKY 14 performance sql-server-2008-r2 filtered-index query-performance

我有以下 SQL 查询:

SELECT
  Event.ID,
  Event.IATA,
  Device.Name,
  EventType.Description,
  Event.Data1,
  Event.Data2
  Event.PLCTimeStamp,
  Event.EventTypeID
FROM
  Event
INNER JOIN EventType ON EventType.ID = Event.EventTypeID
INNER JOIN Device ON Device.ID = Event.DeviceID
WHERE
  Event.EventTypeID IN (3, 30, 40, 41, 42, 46, 49, 50)
  AND Event.PLCTimeStamp BETWEEN '2011-01-28' AND '2011-01-29'
  AND Event.IATA LIKE '%0005836217%'
ORDER BY Event.ID;
Run Code Online (Sandbox Code Playgroud)

我在Event表上也有一个列的索引TimeStamp。我的理解是这个索引没有使用,因为IN()声明。所以我的问题是有没有办法为这个特定的IN()语句建立索引来加速这个查询?

我还尝试将 上Event.EventTypeID IN (2, 5, 7, 8, 9, 14)的索引添加为过滤器TimeStamp,但是在查看执行计划时,它似乎没有使用此索引。对此的任何建议或见解将不胜感激。

下面是图形化计划:

执行计划

这是.sqlplan 文件的链接

Pau*_*ite 18

给定以下一般形式的表格:

CREATE TABLE Device 
(
    ID integer PRIMARY KEY
);

CREATE TABLE EventType
(
    ID integer PRIMARY KEY, 
    Name nvarchar(50) NOT NULL
);

CREATE TABLE [Event]
(
    ID integer PRIMARY KEY, 
    [TimeStamp] datetime NOT NULL, 
    EventTypeID integer NOT NULL REFERENCES EventType, 
    DeviceID integer NOT NULL REFERENCES Device
);
Run Code Online (Sandbox Code Playgroud)

以下索引很有用:

CREATE INDEX f1 
ON [Event] ([TimeStamp], EventTypeID) 
INCLUDE (DeviceID)
WHERE EventTypeID IN (2, 5, 7, 8, 9, 14);
Run Code Online (Sandbox Code Playgroud)

对于查询:

SELECT
  [Event].ID,
  [Event].[TimeStamp],
  EventType.Name,
  Device.ID
FROM
  [Event]
INNER JOIN EventType ON EventType.ID = [Event].EventTypeID
INNER JOIN Device ON Device.ID = [Event].DeviceID
WHERE
  [Event].[TimeStamp] BETWEEN '2011-01-28' AND '2011-01-29'
  AND Event.EventTypeID IN (2, 5, 7, 8, 9, 14);
Run Code Online (Sandbox Code Playgroud)

过滤器满足AND子句要求,索引的第一个键允许[TimeStamp]对过滤器进行查找EventTypeIDs,包括DeviceID列使索引覆盖(因为DeviceID需要连接到Device表)。

完成计划

索引的第二个键 -EventTypeID不是严格要求的(它也可以是一INCLUDEd列);出于此处所述的原因,我已将其包含在密钥中。一般来说,我建议人们至少从过滤索引子句中选择列。INCLUDEWHERE


根据问题中更新的查询和执行计划,我同意 SSMS 建议的更通用的索引在这里可能是更好的选择,除非过滤列表EventTypeIDs是静态的,正如 Aaron 在他的回答中也提到的:

CREATE TABLE Device 
(
    ID integer PRIMARY KEY,
    Name nvarchar(50) NOT NULL UNIQUE
);

CREATE TABLE EventType
(
    ID integer PRIMARY KEY, 
    Name nvarchar(20) NOT NULL UNIQUE,
    [Description] nvarchar(100) NOT NULL
);

CREATE TABLE [Event]
(
    ID integer PRIMARY KEY, 
    PLCTimeStamp datetime NOT NULL,
    EventTypeID integer NOT NULL REFERENCES EventType, 
    DeviceID integer NOT NULL REFERENCES Device,
    IATA varchar(50) NOT NULL,
    Data1 integer NULL,
    Data2 integer NULL,
);
Run Code Online (Sandbox Code Playgroud)

建议的索引(如果合适,声明它是唯一的):

CREATE UNIQUE INDEX uq1
ON [Event]
    (EventTypeID, PLCTimeStamp)
INCLUDE 
    (DeviceID, IATA, Data1, Data2, ID);
Run Code Online (Sandbox Code Playgroud)

执行计划中的基数信息(未记录的语法,请勿在生产系统中使用):

UPDATE STATISTICS dbo.Event WITH ROWCOUNT = 4042700, PAGECOUNT = 400000;
UPDATE STATISTICS dbo.EventType WITH ROWCOUNT = 22, PAGECOUNT = 1;
UPDATE STATISTICS dbo.Device WITH ROWCOUNT = 2806, PAGECOUNT = 28;
Run Code Online (Sandbox Code Playgroud)

更新的查询(在这种特定情况下重复表的IN列表EventType有助于优化器):

SELECT
  Event.ID,
  Event.IATA,
  Device.Name,
  EventType.Description,
  Event.Data1,
  Event.Data2,
  Event.PLCTimeStamp,
  Event.EventTypeID
FROM
  Event
INNER JOIN EventType ON EventType.ID = Event.EventTypeID
INNER JOIN Device ON Device.ID = Event.DeviceID
WHERE
  Event.EventTypeID IN (3, 30, 40, 41, 42, 46, 49, 50)
  AND EventType.ID IN (3, 30, 40, 41, 42, 46, 49, 50)
  AND Event.PLCTimeStamp BETWEEN '2011-01-28' AND '2011-01-29'
  AND Event.IATA LIKE '%0005836217%'
ORDER BY Event.ID;
Run Code Online (Sandbox Code Playgroud)

预计执行计划:

第二个计划

你得到的计划可能会有所不同,因为我使用的是猜测的统计数据。总的一点是尽可能多地给优化器提供信息,并在 400 万行的[Event]表上提供一种高效的访问方法(索引)。


Aar*_*and 8

大部分成本是聚集索引扫描,除非这个表真的很宽或者你真的不需要输出中的所有这些列,我相信 SQL Server 这是当前场景中的最佳路径,没有其他任何改变. 它确实使用范围扫描(标记为 CI 搜索)来缩小它感兴趣的行的范围,但是由于输出,即使使用您创建的过滤索引,它仍然需要查找或 CI 扫描以这个范围为目标,即使在这种情况下,CI 扫描可能仍然是最便宜的(或者至少 SQL Server 是这样估计的)。

执行计划确实告诉你这个索引会很有用:

CREATE NONCLUSTERED INDEX ix_EventTypeID_PLCTimeStamp_WithIncludes
  ON [dbo].[Event] ([EventTypeID],[PLCTimeStamp])
  INCLUDE ([ID],[DeviceID],[Data1],[Data2],[IATA]);
Run Code Online (Sandbox Code Playgroud)

虽然取决于您的数据倾斜,但反过来可能会更好,例如:

CREATE NONCLUSTERED INDEX ix_PLCTimeStamp_EventTypeID_WithIncludes
  ON [dbo].[Event] ([PLCTimeStamp],[EventTypeID])
  INCLUDE ([ID],[DeviceID],[Data1],[Data2],[IATA]);
Run Code Online (Sandbox Code Playgroud)

但我会测试两者以确定哪个更好,如果 - 这些索引中的任何一个与您现在拥有的之间的差异可能只是微不足道(我们不知道太多变量),您必须考虑到额外的索引需要额外的维护,这会显着影响您的 DML 操作(插入/更新/删除)。您也可以考虑按照@SQLKiwi 的建议在此索引中包含过滤条件,但前提是这是您经常搜索的一组 EventTypeID 值。如果该集合随时间发生变化,则过滤后的索引将仅对这个特定查询有用。

由于行数如此之少,我不得不怀疑当前的性能可能有多糟糕?此查询返回 3 行(但没有任何迹象表明它拒绝了多少行)。表中有多少行?