高效地过滤带析取的大集合


9

假设我只有一张桌子

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
);

在此示例中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;

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

我能提出的最好的建议是一个涵盖索引,其中包含每个“可选”列:

CREATE NONCLUSTERED INDEX IX_Ticket_Covering ON Ticket (
    InsertDateTime DESC
) INCLUDE (
    SiteId, StatusId, AssignedId, ReportedById, CategoryId
);

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

  • 选择
    • 过滤
      • 最佳
        • 序列项目(计算标量)
          • 分割
            • 索引搜寻

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

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

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


是否可以将子查询放入临时表或表变量中并以这种方式构建?在我的桌子较大时,有时会被子查询困扰。涵盖索引只能带您走远。
女武神

@Valkyrie效率极低。还应考虑到该查询的变体(不同的参数和不同的可选where子句)可能整天每秒执行几次,并且平均需要在不到100ms的时间内返回结果。我们已经做到了,并且现在还可以。我只是在寻找有关如何继续提高性能以实现可伸缩性的想法。
约瑟夫·戴格尔,2013年

您关心多少使用存储空间?
乔恩·塞格尔

@JonSeigel这取决于多少...但是我想看看任何建议
Joseph Daigle

2
您要获取结果第二页的方法/查询是什么?RowNum BETWEEN 101 AND 200
ypercubeᵀᴹ

Answers:


1

如果此特定工作量是针对该表的大多数查询,则可以考虑:

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

CREATE UNIQUE CLUSTERED INDEX IX_Ticket_Covering ON Ticket (
    InsertDateTime ASC
);

注意事项:

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

优点:

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

1

我过去曾经使用过这种技术。桌子虽然不大,但是搜索条件却更复杂。

这是简短的版本。

CREATE PROC usp_Search
    (
    @StartDate  Date,
    @EndDate    Date,
    @Sites      Varchar(30) = NULL,
    @Assigned   Int = NULL, --Assuming only value possible
    @StartRow   Int,
    @EndRow     Int
    )
AS
DECLARE @TblSites   TABLE (ID Int)
IF @Sites IS NOT NULL
BEGIN
    -- Split @Sites into table @TblSites
END
SELECT  TicketId
FROM    (
        SELECT  TicketId,
                ROW_NUMBER() OVER(ORDER BY InsertDateTime DESC) as RowNum
        FROM    Ticket
                LEFT JOIN @TblSites
                    Ticket.SiteID = @TblSites.ID
        WHERE   InsertDateTime >= @StartDate 
                AND InsertDateTime < @EndDate
                AND (
                    @Assigned IS NULL 
                    OR AssignedId = @Assigned 
                    )
        ) _
WHERE   RowNum BETWEEN @StartRow AND @EndRow;



-1

如果客户端一遍又一遍地以几乎相同的方式进行过滤,则可以为这些查询创建索引。

例如,客户端在SiteId和StatusId上进行过滤,则可以创建其他索引:

CREATE NONCLUSTERED INDEX IX_Ticket_InsertDateTime_SiteId_StatusId ON Ticket     
(InsertDateTime DESC,
 SiteId [ASC/DESC],
 StatusId [ASC/DESC] ) 
 INCLUDE ( ... );

这样,大多数“更常见”的查询可以快速运行。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.