过滤按行版本排序的数据

Pac*_*civ 8 index sql-server index-tuning

我有一个具有以下结构的 SQL 数据表:

CREATE TABLE Data(
    Id uniqueidentifier NOT NULL,
    Date datetime NOT NULL,
    Value decimal(20, 10) NULL,
    RV timestamp NOT NULL,
 CONSTRAINT PK_Data PRIMARY KEY CLUSTERED (Id, Date)
)
Run Code Online (Sandbox Code Playgroud)

不同 Id 的数量范围从 3000 到 50000。
表的大小变化高达超过 10 亿行。
一个 ID 可以覆盖表格的几行至 5%。

该表上执行次数最多的查询是:

SELECT Id, Date, Value, RV
FROM Data
WHERE Id = @Id
AND Date Between @StartDate AND @StopDate
Run Code Online (Sandbox Code Playgroud)

我现在必须在 Id 的子集上实现数据的增量检索,包括更新。
然后我使用了一个请求方案,其中调用者提供特定的 rowversion,检索数据块并使用返回数据的最大 rowversion 值进行后续调用。

我写了这个程序:

CREATE TYPE guid_list_tbltype AS TABLE (Id uniqueidentifier not null primary key)
Run Code Online (Sandbox Code Playgroud)
CREATE PROCEDURE GetData
    @Ids guid_list_tbltype READONLY,
    @Cursor rowversion,
    @MaxRows int
AS
BEGIN
    SELECT A.* 
    FROM (
        SELECT 
            Data.Id,
            Date,
            Value,
            RV,
            ROW_NUMBER() OVER (ORDER BY RV) AS RN
        FROM Data
             inner join (SELECT Id FROM @Ids) Ids ON Ids.Id = Data.Id
        WHERE RV > @Cursor
    ) A 
    WHERE RN <= @MaxRows
END
Run Code Online (Sandbox Code Playgroud)

@MaxRows将范围取决于如何分块的客户会希望他的数据500,000到200万之间。


我尝试了不同的方法:

  1. 索引 (Id, RV) :
    CREATE NONCLUSTERED INDEX IDX_IDRV ON Data(Id, RV) INCLUDE(Date, Value);
Run Code Online (Sandbox Code Playgroud)

使用索引,查询查找RV = @Cursor每个Idin的行@Ids,读取以下行,然后合并结果并排序。
效率则取决于@Cursor价值的相对位置。
如果它接近数据末尾(由 RV 排序),则查询是即时的,否则查询可能需要长达几分钟的时间(永远不要让它运行到最后)。

这种方法的问题@Cursor是要么接近数据的末尾,排序不痛苦(如果查询返回的行数少于 ,甚至不需要@MaxRows)要么它更落后并且查询必须对@MaxRows * LEN(@Ids)行进行排序。

  1. RV 上的索引:
    CREATE NONCLUSTERED INDEX IDX_RV ON Data(RV) INCLUDE(Id, Date, Value);
Run Code Online (Sandbox Code Playgroud)

使用索引,查询查找行,RV = @Cursor然后读取每一行,丢弃未请求的 Id,直到达到@MaxRows
效率则取决于请求的 Id ( LEN(@Ids) / COUNT(DISTINCT Id))的百分比及其分布。
更多的请求 Id % 意味着更少的丢弃行,这意味着更有效的读取,更少请求的 Id % 意味着更多丢弃的行,这意味着对相同数量的结果行进行更多读取。

这种方法的问题在于,如果请求的 Id 只包含几个元素,它可能必须读取整个索引才能获得所需的行。

  1. 使用过滤索引或索引视图
    CREATE NONCLUSTERED INDEX IDX_RVClient1 ON Data(Id, RV) INCLUDE(Date, Value)
    WHERE Id IN (/* list of Ids for specific client*/);
Run Code Online (Sandbox Code Playgroud)

或者

    CREATE VIEW vDataClient1 WITH SCHEMABINDING
    AS
    SELECT
        Id,
        Date,
        Value,
        RV
    FROM dbo.Data
    WHERE Id IN (/* list of Ids for specific client*/)
Run Code Online (Sandbox Code Playgroud)
    CREATE UNIQUE CLUSTERED INDEX IDX_IDRV ON vDataClient1(Id, Rv);
Run Code Online (Sandbox Code Playgroud)

这种方法允许完全高效的索引和查询执行计划,但有缺点: 1. 实际上,我必须实现动态 SQL 来创建索引或视图,并修改请求过程以使用正确的索引或视图。2. 我将不得不维护现有客户端的一个索引或视图,包括存储。3. 每次客户必须修改他请求的 Id 列表时,我将不得不删除索引或视图并重新创建它。


我似乎找不到适合我需要的方法。
我正在寻找更好的想法来实现增量数据检索。这些想法可能意味着修改请求方案或数据库模式,尽管我更喜欢更好的索引方法(如果有的话)。

Pau*_*ite 5

一种解决方案是让客户端应用程序记住rowversion每个 ID的最大值。用户定义的表类型将更改为:

CREATE TYPE
    dbo.guid_list_tbltype
AS TABLE 
    (
    Id      uniqueidentifier PRIMARY KEY, 
    LastRV  rowversion NOT NULL
    );
Run Code Online (Sandbox Code Playgroud)

然后可以重写该过程中的查询以使用该APPLY模式(请参阅我的 SQLServerCentral 文章第 1部分第 2 部分- 需要免费登录)。这里良好性能的关键是ORDER BY- 它避免了嵌套循环连接上的无序预取。的RECOMPILE,以允许优化器来查看表变量的在编译时的基数(可能导致期望的并行计划)是必要的。

ALTER PROCEDURE dbo.GetData

    @IDs        guid_list_tbltype READONLY,
    @MaxRows    bigint

AS
BEGIN

    SELECT TOP (@MaxRows)
        d.Id,
        d.[Date],
        d.Value,
        d.RV
    FROM @Ids AS i
    CROSS APPLY
    (
        SELECT
            d.*
        FROM dbo.Data AS d
        WHERE
            d.Id = i.Id
            AND d.RV > i.LastRV
    ) AS d
    ORDER BY
        i.Id,
        d.RV
    OPTION (RECOMPILE);

END;
Run Code Online (Sandbox Code Playgroud)

你应该得到一个像这样的执行后查询计划(估计计划将是串行的):

查询计划