在子选择中选择 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 变量上。
不过,我仍然认为添加索引将是最不糟糕的选择。
我有一个包含课程的数据库。每门课程包含一组节点,一些节点包含一组学生的答案。答案 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 变量上。
不过,我仍然认为添加索引将是最不糟糕的选择。