过滤按行版本排序的数据


8

我有一个具有以下结构的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)
)

不同的Id的数量在3000到50000之间。
表的大小变化到十亿行以上。
一个ID最多可以覆盖表格的5%至几行。

此表上最常执行的查询是:

SELECT Id, Date, Value, RV
FROM Data
WHERE Id = @Id
AND Date Between @StartDate AND @StopDate

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

我已经写了这个程序:

CREATE TYPE guid_list_tbltype AS TABLE (Id uniqueidentifier not null primary key)
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

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


我尝试了不同的方法:

  1. 在(Id,RV)上建立索引:
    CREATE NONCLUSTERED INDEX IDX_IDRV ON Data(Id, RV) INCLUDE(Date, Value);

使用索引,查询征求行,其中RV = @Cursor每个Id@Ids,阅读下面的行然后合并的结果和排序。
效率取决于@Cursor价值的相对位置。
如果它接近数据的末尾(按RV排序),则查询是瞬时的,如果不是,则查询可能要花费几分钟(永远不要让它运行到末尾)。

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

  1. RV索引:
    CREATE NONCLUSTERED INDEX IDX_RV ON Data(RV) INCLUDE(Id, Date, Value);

查询使用索引来查找该行,RV = @Cursor然后在其中读取每一行,并丢弃未请求的ID直到到达@MaxRows
效率则取决于所请求的Ids(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*/);

要么

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

这种方法可以实现非常有效的索引编制和查询执行计划,但有以下缺点:1.实际上,我将必须实现动态SQL以创建索引或视图,并修改请求过程以使用正确的索引或视图。2.我将必须维护现有客户端(包括存储)的一个索引或视图。3.每次客户必须修改其请求的ID列表时,我都必须删除索引或查看并重新创建它。


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


stackoverflow.com/questions/11586004/…的交叉发布。我暂时删除了Oracle版本,因为我发现ORA_ROWSCN无法建立索引(并且几乎无法通过建立索引的实例化视图)。
Paciv

日期字段如何适合?表格中可以更新具有特定ID和日期的行吗?如果是这样,日期是否还会更新(例如附加时间戳?)
8kb,2012年

似乎对于GetData()尝试而言,排序依据应包括ID(按RV,Id排序)。您可以评论使用(Rv,Id)索引吗?同样在上一次调用中使用“>” max rowversion似乎在行具有相同rowversion的情况下会丢失大块之间的记录(不是吗?)。
crokusek 2012年

@ 8kb:在表上运行的更新语句仅修改该Value列。@crokusek:不会按RV排序,ID而不是RV只会增加排序工作量,而没有任何好处,我不理解您的评论背后的原因。根据我的阅读,RV应该是唯一的,除非将数据专门插入到该列中,而应用程序则不会。
帕西夫2012年

客户端是否可以按(Id,Rv)顺序接受结果,并除了LastRowVersion参数之外还提供LastId参数,以消除跨ID的RV排序?我之前的评论都是基于RV重复的假设。每个客户端的筛选索引看起来很有趣。
crokusek 2012年

Answers:


5

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

CREATE TYPE
    dbo.guid_list_tbltype
AS TABLE 
    (
    Id      uniqueidentifier PRIMARY KEY, 
    LastRV  rowversion NOT NULL
    );

然后可以将过程中的查询重写为使用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;

您应该获得像这样的执行后查询计划(估计计划将是串行的):

查询计划


正确,一种设计变更解决方案是让客户记住MAX(RV)每个Id(或内部应用程序可以记住所有Id / RV对的预订系统),而我将此模式用于其他客户。另一种解决方案是强制客户端始终检索所有Id(这使索引问题变得微不足道)。它仍然没有满足特定需求的问题:仅由客户端提供一个全局计数器,即可增量检索Ids的子集。
Paciv

2

如果可能的话,我将重新设计桌子。如果我们可以将VersionNumber作为没有间隙的增量整数,则检索下一个块的任务将是完全琐碎的范围扫描。我们需要的是以下索引:

CREATE NONCLUSTERED INDEX IDX_IDRV ON Data(Id, VersionNumber) INCLUDE(Date, Value);

当然,我们需要确保VersionNumber以1开头且没有空格。使用约束很容易做到这一点


您是指全局还是本地ID VersionNumber?无论哪种情况,我都看不出这对问题有什么帮助,您能否进一步阐述?
2012年

0

我会做的:

在这种情况下,您的PK应该是自动增加的“代理密钥”标识字段。
由于您已经身家数十亿,所以最好选择BigInt。
我们称之为DataID
这将:

  • 将8个字节添加到群集索引中的每个记录。
  • 在每个非聚集索引中的每个记录上保存16个字节。
  • 您拥有的是一个“自然键”:具有DateTime(8个字节)的UniqueIdentifyer(16个字节)。
  • 每个索引记录中有24个字节,可引用回聚簇索引!
  • 这就是为什么我们将代理键作为较小的增量整数。


将新的BigInt PK(DataID)设置为使用聚簇索引:
这将:

  • 确保最近创建的记录放在末尾。
  • 允许使用其他非聚集索引进行更快的索引。
  • 允许将来作为FK扩展到其他表。


在(Date,Id)周围创建一个非聚集索引。
这将:

  • 加快您最常用的查询。
  • 您可以添加“值”,但是它将增加索引的大小,从而使其变慢。
  • 我建议在索引的内部和外部进行尝试,以查看性能是否存在巨大差异。
  • 如果您要添加“不包含”,我建议不要使用它。
  • 只需像这样(Date,Id,Value)坚持下去-但前提是您的测试表明它可以提高性能。


在(RV,ID)上创建一个非聚集索引。
这将:

  • 始终保持索引尽可能小。
  • 除非您发现索引中包含日期和值会导致疯狂的巨大性能提升,否则建议您不要使用它们以节省磁盘空间。首先尝试一下没有它们的情况。
  • 如果要添加日期或值,请不要使用“包含”,而应将它们添加到索引的顺序中。
  • 多亏了集群PK中新插入的DataID增量,您最近的RV通常会显示在末尾(除非您一直在更新大量数据)。
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.