是否可以使用过滤后的索引作为提示来优化SELECT COUNT(*)查询以实现恒定速度



我想统计所有不紧急且订单状态为1(已发货)的订单。

这应该是一个非常简单的优化查询。我想在Orders表上放一个简单的过滤索引来覆盖这个查询,使其成为一个常量时间/O(1)操作。然而,当我查看查询计划时,它看起来像是在使用索引扫描,这毫无意义。理想情况下,此查询应该只返回索引中的项数。

该表如下所示(简化以达到本质):

CREATE TABLE [dbo].[Orders](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [IsUrgent] [bit] NOT NULL,
    [Status] [tinyint] NOT NULL
 CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED ( [Id] ASC )

我创建了这个过滤索引:

CREATE INDEX IX_Orders_ShippedNonUrgent ON Orders(Id) WHERE IsUrgent = 0 AND Status = 1;

现在,当我进行这个查询时:

SELECT COUNT(*) FROM Orders WHERE IsUrgent = 0 AND Status = 1

我看到查询计划使用IX_Orders_ShippedNonEmergency,但它正在进行索引扫描,并在Orders中的150000行中执行大约200次读取。

假设过滤后的索引是最新的,是否可以始终在恒定时间内运行此查询?理想情况下,它应该只执行1次读取以获得索引的大小。

如果我切换到这样的非过滤索引:

CREATE INDEX IX_Orders_IsUrgentStatus ON Orders(IsUrgent, Status);

查询计划使用索引查找,但执行的读取次数仍然比回答此简单查询所需的读取次数多得多。

更新

我能做这个

SELECT TOP 1 rows FROM sys.partitions p
INNER JOIN sys.indexes i
ON i.name = 'IX_Orders_ShippedNonUrgent'
AND i.object_id = p.object_id
AND i.index_id = p.index_id

并在9次读取中获得结果,但似乎应该有一种更简单、不那么脆弱的方法来使用简单的COUNT(*)查询。

我想要的似乎不可能实现。Nikola Markovinović在评论中给出了最好的答案,那就是忘记过滤后的索引,转而使用索引视图:

CREATE VIEW [dbo].vw_Orders_TotalShippedNonUrgent WITH SCHEMABINDING 
AS 
SELECT COUNT_BIG(*) AS TotalOrders 
  FROM dbo.Orders WHERE IsUrgent = 0 AND Status = 1;

带有

CREATE UNIQUE CLUSTERED INDEX IX_vw_Orders_TotalShippedNonUrgent ON vw_Orders_TotalShippedNonUrgent(TotalOrders);

这会强制为我想要的每个汇总统计信息创建视图及其索引,并重写查询以询问视图,而不是简单的方法,但它的读取速度很快,只有2次。

我暂时不提这个问题,以防有人能找到同样快速的更简单的方法。

最新更新