分页性能,带有子查询、内连接和 where

MSM*_*MSM 6 performance sql-server sql-server-2012 query-performance

我正在尝试优化以下查询(这是我能想到的最简化的版本):

SELECT tr.Id, StatusDate
FROM (
    SELECT tr.Id, tr.StatusDate
    FROM mon.ArchivedTaskResults_201504 as tr WITH (NOLOCK)
    INNER JOIN mon.ViewDevicesWithGroups dev WITH (NOLOCK) ON tr.DeviceId = dev.Id
    WHERE tr.ClientId = 4 AND dev.Deleted = 0
) AS tr        
ORDER BY StatusDate DESC
OFFSET 1000000 rows
FETCH NEXT 25 ROWS ONLY
Run Code Online (Sandbox Code Playgroud)

简单查询执行计划

问题是,查询性能与 OFFSET 成正比 - 对于 offset=0 查询在 0.0 秒内执行,但对于 offset=1000000 执行时间约为 23 秒(如果偏移量更大,则可能需要几分钟)。

我几乎可以肯定我的问题可以通过 ArchivedTaskResults 表上的适当聚集索引来解决,但是在尝试了几个小时之后我仍然没有找到好的索引。

ArchivedTaskResults 表真的很大,大约有 50000000 行(50 M)

附加信息:

如果有人能解决我上面描述的问题,我会非常高兴,但说实话,我的真实查询更加奇怪(免责声明:我不是设计这个数据库的人):

SELECT tr.Id, StatusDate
FROM (
    (
        SELECT tr.Id, StatusDate
        FROM mon.TaskResults as tr WITH (NOLOCK)
        INNER JOIN mon.ViewDevicesWithGroups dev WITH (NOLOCK) ON tr.DeviceId = dev.Id
        WHERE tr.ClientId = 4 AND dev.Deleted = 0
    ) UNION ALL (
        SELECT tr.Id, tr.StatusDate
        FROM mon.ArchivedTaskResults_201504 as tr WITH (NOLOCK)
        INNER JOIN mon.ViewDevicesWithGroups dev WITH (NOLOCK) ON tr.DeviceId = dev.Id
        WHERE tr.ClientId = 4 AND dev.Deleted = 0
    ) UNION ALL (
        SELECT tr.Id, tr.StatusDate
        FROM mon.ArchivedTaskResults_201505 as tr WITH (NOLOCK)
        INNER JOIN mon.ViewDevicesWithGroups dev WITH (NOLOCK) ON tr.DeviceId = dev.Id
        WHERE tr.ClientId = 4 AND dev.Deleted = 0
    )
) AS tr        
ORDER BY StatusDate DESC
OFFSET 1000000 ROWS
FETCH NEXT 25 ROWS 
Run Code Online (Sandbox Code Playgroud)

复杂查询执行计划

这要复杂得多,因为即使是 offset=0 的查询也有很长的执行时间(我猜是因为 sql server 不知道 ArchivedTaskResults_201505 出现在 ArchivedTaskResults_201504 之后,并试图在内存中对它们进行排序,但这只是我的盲注) .

如果有人设法帮助我进行该查询,那将超出我最疯狂的梦想,但如果由于奇怪的数据库设计而无法实现,我想我可以用软件解决它(我可以从我的应用程序中一次查询一个表,而不是在 SQL 中做所有事情。或者为此使用存储过程)-但前提是我的第一个查询可以改进。

提前致谢。

Jon*_*des 4

我认为您在使用时无法获得良好的性能OFFSET。数据库必须搜索内部查询的 1,000,025 行输出;即使您在TaskResults系统上有一个良好的聚集索引,也不确定它是否可以跳到日期 X。

但你做了!假设这是针对某种 GUI,请记下上StatusDate一个查询中最早的查询,然后使用它来调整下一页:

SELECT
    tr.Id, StatusDate
FROM
    (
    SELECT tr.Id, tr.StatusDate
    FROM mon.ArchivedTaskResults_201504 as tr WITH (NOLOCK)
        INNER JOIN mon.ViewDevicesWithGroups dev WITH (NOLOCK) ON tr.DeviceId = dev.Id
    WHERE tr.ClientId = 4 AND dev.Deleted = 0
        AND
            (
            -- Retrieve only records from before the previous page
            tr.StatusDate < @PrevStatusDate
            OR (tr.StatusDate = @PrevStatusDate AND tr.Id < @PrevID) 
            )
    ) AS tr        
ORDER BY StatusDate, Id DESC
FETCH NEXT 25 ROWS ONLY
Run Code Online (Sandbox Code Playgroud)

因此,如果第 #123 页以 2015/05/01 结束,记录 #234,则您需要考虑 2015/04/30 或更早的所有记录,或者同样来自 2015/05/01 但属于记录 #1 的所有记录..#233。

这应该适用于更复杂的 UNION 查询,但“真正的”分区可能比这种滚动分区更容易。

如果StatusDate是唯一的,或者偶尔在两个相邻页面上显示相同的记录是可以接受的,则可以删除 和@PrevIDORDER BY Id。如果Id总是增加,您可以过滤掉它并跳过StatusDate

请记住,如果在基础数据中添加、删除或重新排序记录,则像这样检索页面可以轻松地跳过一条记录或包含同一记录两次。但这是另一个话题了。