使用析取有效过滤大集合

Jos*_*gle 9 index sql-server

假设我有一张桌子

CREATE TABLE Ticket (
    TicketId int NOT NULL,
    InsertDateTime datetime NOT NULL,
    SiteId int NOT NULL,
    StatusId tinyint NOT NULL,
    AssignedId int NULL,
    ReportedById int NOT NULL,
    CategoryId int NULL
);
Run Code Online (Sandbox Code Playgroud)

在这个例子中TicketId是主键。

我希望用户能够针对该表创建“部分临时”查询。我说部分是因为查询的一些部分将始终固定:

  1. 查询将始终InsertDateTime
  2. 查询将始终 ORDER BY InsertDateTime DESC
  3. 查询将分页结果

用户可以选择过滤任何其他列。他们可以过滤无、一个或多个。对于每一列,用户可以从一组值中进行选择,这些值将作为析取应用。例如:

SELECT
    TicketId
FROM (
    SELECT
        TicketId,
        ROW_NUMBER() OVER(ORDER BY InsertDateTime DESC) as RowNum
    FROM Ticket
    WHERE InsertDateTime >= '2013-01-01' AND InsertDateTime < '2013-02-01'
      AND StatusId IN (1,2,3)
      AND (CategoryId IN (10,11) OR CategoryId IS NULL)
    ) _
WHERE RowNum BETWEEN 1 AND 100;
Run Code Online (Sandbox Code Playgroud)

现在假设该表有 100,000,000 行。

我能想到的最好的方法是包含每个“可选”列的覆盖索引:

CREATE NONCLUSTERED INDEX IX_Ticket_Covering ON Ticket (
    InsertDateTime DESC
) INCLUDE (
    SiteId, StatusId, AssignedId, ReportedById, CategoryId
);
Run Code Online (Sandbox Code Playgroud)

这给了我一个查询计划如下:

  • 选择
    • 筛选
      • 最佳
        • 序列项目(计算标量)
          • 部分
            • 索引搜索

看起来还不错。大约 80%-90% 的成本来自 Index Seek 操作,这是理想的。

是否有更好的策略来实现这种搜索?

我不一定要将可选过滤卸载到客户端,因为在某些情况下,“固定”部分的结果集可能是 100 或 1000。然后,客户端还将负责排序和分页,这对客户端来说可能太多了。

Mat*_*att 1

如果此特定工作负载是针对表的大部分查询,您可能会考虑:

ALTER TABLE Ticket ADD CONSTRAINT PK_Ticket PRIMARY KEY NONCLUSTERED (TicketId);

CREATE UNIQUE CLUSTERED INDEX IX_Ticket_Covering ON Ticket (
    InsertDateTime ASC
);
Run Code Online (Sandbox Code Playgroud)

注意事项:

  • 你可以使用 datetime2 (SQL 2008+;灵活的精度)
  • InsertDateTime 在您的精度范围内是否唯一
  • 如果时间不受限制,unique sql将添加一个int类型的隐藏uniquifier列。这被添加到所有非聚集索引中,以便它们可以引用正确的聚集记录

优点:

  • 将新行添加到表末尾
  • 防止将可选过滤列写入两次(一次在集群中,一次在包含的索引叶上)
  • 您的大部分时间仍然会使用或多或少的文件管理器进行集群索引搜索。
  • 然后为最流行的列对添加其他非聚集索引