在子选择中选择 n 个最新行的性能问题

Performance issue on selecting n newest rows in subselect

我有一个包含课程的数据库。每门课程包含一组节点,一些节点包含一组学生的答案。答案 table 看起来(简化)如下:

回答

id  | courseId |  nodeId |  answer
------------------------------------------------
 1  |   1      |   1     |  <- text ->
 2  |   2      |   2     |  <- text ->
 3  |   1      |   1     |  <- text ->
 4  |   1      |   3     |  <- text ->
 5  |   2      |   2     |  <- text ->
..  |  ..      |   ..    |  ..

当老师打开一门课程时(即courseId = 1),我想选择最近收到最多答案的节点。我可以使用以下查询来做到这一点:

with Answers as
(
   select top 50 id, nodeId from Answer A where courseId=1 order by id desc
)
select top 1 nodeId from Answers group by nodeId order by count(id) desc

或同样使用此查询:

select top 1 nodeId from 
    (select top 50 id, nodeId from Answer A where courseId=1 order by id desc)
    group by nodeId order by count(id) desc

在这两个查询中,最新的 50 个答案(具有最高 ID)被 selected,然后按 nodeId 分组,这样我就可以选择频率最高的答案。但是,我的问题是查询非常慢。如果我只运行子select,用时不到一秒,分组50行应该很快,但是当我运行整个查询大约需要10秒!我的猜测是 sql 服务器首先执行 select 和分组,然后执行前 50 名和前 1 名,在这种情况下会导致糟糕的性能。

那么,我怎样才能重写查询以提高效率呢?

您可以添加索引以提高查询效率。对于此查询:

with Answers as (
      select top 50 id, nodeId
      from Answer A
      where courseId = 1
      order by id desc
     )
select top 1 nodeId
from Answers
group by nodeId
order by count(id) desc;

最好的索引是Answer(courseId, id, nodeid)

为了更有洞察力,我们需要查看 table 上的索引和您获得的执行计划 (内部查询的一个计划,一个计划完整查询).

我什至建议在添加本页其他地方提到的索引后再次进行相同的分析。

如果没有这些信息,我们唯一可以推荐的就是反复试验。

例如,尽量避免使用 TOP (这应该无关紧要,但我们只是在猜测,因为我们看不到您的索引和执行计划)

WITH
    Answers AS
(
    SELECT
        ROW_NUMBER() OVER (ORDER BY id DESC)   AS rowId,
        id,
        nodeId
    FROM
        Answer
    WHERE
        courseId = 1
),
    top50 AS
(
    SELECT
        nodeId,
        COUNT(*)   AS row_count
    FROM
        Answers
    WHERE
        rowId <= 50
    GROUP BY
        nodeId
),
    ranked AS
(
    SELECT
        ROW_NUMBER() OVER (ORDER BY row_count DESC, nodeId DESC)  AS ordinal,
        nodeID
    FROM
        top50
)
SELECT
    nodeID
FROM
    ranked
WHERE
    oridinal = 1

这非常夸张,但在功能上与您在 OP 中的相同,但与可能 获得不同的执行计划有很大不同。

或者 (不是很好),只需将内部查询的结果放入 table 变量,然后 运行 外部查询在 table 变量上。

不过,我仍然认为添加索引将是最不糟糕的选择。