2015-08-19 2 views
1

У меня есть база данных с курсами. Каждый курс содержит набор узлов, а некоторые узлы содержат набор ответов от студентов. Смотрит ответу таблица (упрощенный), как это:Ошибка производительности при выборе n новых строк в подзапросе

Ответ

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 

В обоих querys новейшие 50 ответов (с самыми высокими идентификаторами) выбираются, а затем группируются по NodeId так Я могу выбрать тот, который имеет самую высокую частоту. Моя проблема, однако, в том, что запрос выполняется очень медленно. Если я выполняю только подзапрос, он занимает меньше секунды, и группировка 50 строк должна быть быстрой, но когда я запускаю весь запрос, это занимает около 10 секунд! Я предполагаю, что сервер sql сначала выбирает и группирует, а затем делает верхнюю часть 50 и первую 1, что в этом случае приводит к ужасной производительности.

Итак, как я могу переписать запрос как эффективный?

+0

Какие индексы у вас есть на этом столе? Я бы рекомендовал иметь один составной индекс на '(course_id, id)'. Что касается вашего предположения о том, что делает сервер sql, испытайте это эмпирически, получив планы выполнения и сравнив их. Вы можете обнаружить, что существование внешнего запроса изменяет план внутренней части запроса.Вы также можете обнаружить, что добавление указанного индекса полностью изменяет план. – MatBailie

ответ

2

Вы можете добавить индексы, чтобы повысить эффективность ваших запросов. Для этого запроса:

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).

+0

Да, конечно, но это на самом деле не ответит на мой вопрос. Поскольку внутренний запрос занимает менее секунды, а весь запрос умножает это время более чем на 10, запрос, очевидно, приводит к большому количеству накладных расходов под капотом, и, насколько я знаю, индексы не будут исправлять это. Более того, мы хотим использовать как можно меньше индексов, так как таблица записывается на все время. –

+1

@ BjørnOveThue - вы, вероятно, должны проверить свои предположения, прежде чем утверждать свои выводы. SQL Server не создает план выполнения для внутреннего запроса независимо от внешнего запроса и не генерирует планы выполнения независимо от индексов. Положите другой путь; добавление внешнего запроса может изменить план выполнения, и добавление индексов может снова изменить план выполнения. Я бы предложил попробовать это решение, включая индекс, и проанализировать планы выполнения, которые вы получаете; вы все же можете обнаружить, что это работает для вас. – MatBailie

+0

OK! Добавление индекса, похоже, также изменило план выполнения. Задача решена! :) –

0

Чтобы быть более проницательным, нам нужно будет увидеть индексы на этой таблице и планы выполнения, которые вы получаете (один план для внутреннего запроса на свой счет, один план для полного запроса).

Я бы даже рекомендовал повторить тот же анализ, добавив индекс, упомянутый в другом месте на этой странице.

Без этой информации единственное, что мы можем порекомендовать, - это проб и ошибок.

Например, попробуйте избегать использования 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, но достаточно отличается от потенциально получить другой план выполнения.

В качестве альтернативы (и не очень приятно) просто поместите результаты вашего внутреннего запроса в переменную таблицы, затем запустите внешний запрос в переменной таблицы.

Я все еще ожидаю, однако, что добавление индекса будет наименее худшим вариантом.

+0

OK! Добавление индекса, похоже, также изменило план выполнения. Задача решена! :) –

 Смежные вопросы

  • Нет связанных вопросов^_^