帮助性能调优主/详细信息(类似于收件箱的电子邮件)SQL 查询

Tim*_*hel 9 performance sql-server ssms query-performance

过去几天我一直在搜索、观看视频,我想我已经尽可能地摸索了。给出我下面的例子,我正在寻找更具体的方向。

我有两张桌子正在处理。MessageThreads(400k 条记录)和 Messages(1M 条记录)。它们的模式如下所示。

消息表 在此处输入图片说明

消息线程索引

https://gist.github.com/timgabrhel/0a9ff88160ebc9e40559e1e10ecc7ee4

消息索引

https://gist.github.com/timgabrhel/d649074cbe82016e8a90f918c58c4764

我正在尝试提高我们的主要“收件箱”查询的性能。想想您的电子邮件提供商的收件箱。您将看到一个线程列表,一些是新的,一些是已读的,按日期排序,还可以预览最近发送的消息,无论是发送给您还是来自您。最后,这个查询有一个分页元素。默认情况下,我们需要 11 个项目。10 用于显示页面,+1 以了解下一页是否还有更多内容。

对于我们的一些长期用户,他们最多可以拥有 40K 条消息。

在过去的几天里,这个查询出现了许多不同的形式,但这就是我所了解的。我已经OUTER APPLY试过了,但我看到执行时间和统计数据更糟。

SET STATISTICS IO ON; /* And turn on the Actual Excecution Plan */

declare @UserId bigint
set @UserId = 9999

; WITH cte AS (
    SELECT
        ROW_NUMBER() OVER (ORDER BY SendDate DESC) AS RowNum, 
        MT.MessageThreadId, 
        MT.FromUserHasArchived, 
        MT.ToUserHasArchived, 
        MT.Created, 
        MT.ThreadStartedBy, 
        MT.ThreadSentTo, 
        MT.[Subject], 
        MT.CanReply, 
        MT.FromUserDeleted, 
        MT.ToUserDeleted,              
        LM.MessageId, 
        LM.Deleted, 
        LM.FromUserId, 
        LM.ToUserId, 
        LM.[Message], 
        LM.SendDate, 
        LM.ReadDate
    FROM MessageThreads MT 
    -- join the most recent non-deleted message where this user is the sender or receiver
    LEFT OUTER JOIN 
    (
        SELECT RANK() OVER (PARTITION BY MessageThreadId ORDER BY SendDate DESC) r, * 
        FROM [Messages] 
        WHERE (FromUserId=@UserId OR ToUserId=@UserId) 
        AND (Deleted=0)
    ) LM ON (LM.MessageThreadId = MT.MessageThreadId AND LM.r = 1) 
    --WHERE MT.ThreadSentTo=@UserId OR MT.ThreadStartedBy=@UserId   
)
SELECT
    cte.*,
    UserFrom.FirstName AS UserFromFirstName, 
    UserFrom.LastName AS UserFromLastName, 
    UserFrom.Email AS UserFromEmail,                  
    UserTo.FirstName AS UserToFirstName, 
    UserTo.LastName AS UserToLastName, 
    UserTo.Email AS UserToEmail  
FROM cte
LEFT OUTER JOIN Users AS UserFrom ON cte.FromUserId=UserFrom.UserId 
LEFT OUTER JOIN Users AS UserTo ON cte.ToUserId=UserTo.UserId 
WHERE RowNum >= 1 
AND RowNum <= 11   
ORDER BY RowNum ASC
Run Code Online (Sandbox Code Playgroud)

上述查询的统计信息(SSMS 中的执行时间约 2 秒)。这个执行时间是可以接受的,但统计数据感觉不太理想,在查看实际执行计划时更是如此。 查询统计

执行计划链接在这里 https://gist.github.com/timgabrhel/f8d919d5728e965623fbd953f7a219ef

我发现的一个大问题是 MessageThreads 表上的 400k 行索引扫描。大概这是因为主SELECT X FROM MessageThreads查询没有过滤器。当我对其应用谓词时(从查询中取消注释 WHERE),统计数据大大改善(如下),但时间在 SSMS 中从 ~2 秒跳到 ~18 秒。

查询统计 2

查询上的问题区域是 MessageThreads 谓词

执行计划 https://gist.github.com/timgabrhel/1383ff9362567fdf41ba011dead63ceb

先感谢您!

Lau*_*gil 4

一些想法:

  1. 您的 WHERE 子句需要支持索引

WHERE MT.ThreadSentTo=@UserId OR MT.ThreadStartedBy=@UserId实际上需要两个索引才能高效 - 一个位于 ThreadSentTo 字段,另一个位于 ThreadStartedBy 字段。否则,SQL 引擎将执行全表扫描以检索正确的线程。

  1. 使用 OFFSET...NEXT N ROWS ONLY 而不是 ROW_NUMBER()

从 SQL 2012 开始,SQL Server 添加了一个新的构造来处理分页。这工作起来是这样的:

DECLARE @PageNumber int = 20
DECLARE @RowsPerPage int = 15

SELECT *
FROM MyTable T
INNER JOIN MyDetailTable D
    ON T.MyTableID = D.MyTableID
OFFSET (@PageNumber - 1) * @RowsPerPage ROWS
FETCH NEXT @RowsPerPage ROWS ONLY
Run Code Online (Sandbox Code Playgroud)

在这种情况下,查询将跳过前 285 ((20-1)*15) 行,并检索接下来的 15 行。对于正常分页,这是比旧的 RowNumber() 过滤器更快的分页方法。