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万之间。
我尝试了不同的方法:
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)行进行排序。
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 只包含几个元素,它可能必须读取整个索引才能获得所需的行。
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 列表时,我将不得不删除索引或视图并重新创建它。
我似乎找不到适合我需要的方法。
我正在寻找更好的想法来实现增量数据检索。这些想法可能意味着修改请求方案或数据库模式,尽管我更喜欢更好的索引方法(如果有的话)。
一种解决方案是让客户端应用程序记住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)
你应该得到一个像这样的执行后查询计划(估计计划将是串行的):
