Эффективный способ получения @@ rowcount из запроса с использованием row_number - PullRequest
30 голосов
/ 24 июня 2009

У меня дорогой запрос с использованием функции row_number over () в SQL Server 2005. Я возвращаю только подсписок этих записей, поскольку запрос разбит на страницы. Однако я хотел бы также вернуть общее количество записей, а не только разбитое на страницы подмножество. Эффективный запуск запроса дважды для получения подсчета исключен.

Выбор количества (*) также не подлежит обсуждению, так как производительность абсолютно ужасна, когда я попробовал это.

Что бы мне действительно понравилось, так это @@ ROW_NUMBERROWCOUNT: -)

Ответы [ 4 ]

36 голосов
/ 08 декабря 2010

За эти годы куча пота разработчиков превратилась в эффективные постраничные наборы результатов. Тем не менее, нет единого ответа - это зависит от вашего варианта использования. Часть варианта использования - эффективная загрузка вашей страницы, часть - выяснение количества строк в полном наборе результатов. Извините, если я немного забочусь на пейджинг, но оба довольно тесно связаны между собой.

Существует множество стратегий, большинство из которых являются плохими, если у вас есть какой-либо объем данных и они не соответствуют сценарию использования. Хотя это не полный список, ниже приведены некоторые варианты .....

Запустить отдельно Count(*)

  • запустить отдельный запрос, который выполняет простой «выбор количества (*) из MyTable»
  • просто и легко для маленького стола
  • хорошо для нефильтрованной большой таблицы, которая либо узкая, либо имеет компактный некластеризованный индекс, который вы можете использовать
  • выходит из строя, когда у вас сложные WHERE/JOIN критерии, потому что выполнение WHERE/JOIN дважды стоит дорого.
  • разбивает на широкий индекс, потому что количество чтений увеличивается.

Объединение ROW_Number() OVER() и COUNT(1) OVER(PARTITION By 1)

  • Это было предложено @RBarryYoung. Преимущество простоты реализовать и очень гибкий.
  • Недостатком является то, что существует множество причин, по которым это может быстро стать чрезвычайно дорогим.
  • Например, в БД, в которой я сейчас работаю, есть таблица Media с около 6000 строк. Он не особенно широк, имеет целочисленную кластеризованную PK и компактный уникальный индекс. Тем не менее, простой COUNT(*) OVER(PARTITION BY 1) as TotalRows приводит к ~ 12 000 операций чтения. Сравните это с простым SELECT COUNT(*) FROM Media - 12 чтений. Wowzers.

ОБНОВЛЕНИЕ - проблема чтения, о которой я упомянул, немного красна. Оказывается, что с оконными функциями единица измерения, используемая для измерения показаний, является довольно смешанной. В результате получается огромное количество операций чтения. Вы можете увидеть больше по этой проблеме здесь: Почему логические чтения для оконных агрегатных функций так высоки?

Temp Tables / Table Variables

  • Существует множество стратегий, которые берут набор результатов и вставляют соответствующие ключи или сегменты результатов во временные таблицы / переменные таблиц.
  • Для наборов результатов малого / среднего размера это может обеспечить отличные результаты.
  • Этот тип стратегии работает практически на любой платформе / версии SQL.
  • Работать с результирующим набором несколько раз (довольно часто требуется) также просто.
  • Недостатком является то, что при работе с большими наборами результатов ... вставка нескольких миллионов строк во временную таблицу требует определенных затрат.
  • Ситуация усугубляется тем, что при больших объемах системы давление на TempDB может быть достаточно важным фактором, и временные таблицы эффективно работают в TempDB.

сумма по Гауссу / двухрядное число

  • Эта идея опирается на подмножество того, что вычислил математик Гаусс (как суммировать ряд чисел). Подмножество - как получить количество строк в любой точке таблицы.
  • Из серии чисел (Row_Number()) число строк от 1 до N равно (N + 1) - 1. Больше объяснений в ссылках.
  • Формула выглядит так, как будто бы она получилась равной N, но если вы придерживаетесь формулы, происходит интересное, вы можете вычислить количество строк на странице в середине таблицы.
  • Чистый результат - вы делаете ROW_Number() OVER(Order by ID) и ROW_Number() OVER(Order by ID DESC), затем складываете два числа и вычитаете 1.
  • На примере моей таблицы мультимедиа количество операций чтения уменьшилось с 12 000 до 75.
  • На большой странице вы в конечном итоге повторяли данные много раз, но смещение при чтении может стоить того.
  • Я не проверял это в слишком многих сценариях, поэтому он может развалиться в других сценариях.

Верх (@n) / SET ROWCOUNT

  • Это не конкретные стратегии как таковые, а оптимизации, основанные на том, что мы знаем об оптимизаторе запросов.
  • Творчески используя Top (@n) [top может быть переменной в SQL 2008] или SET ROWCOUNT может уменьшить ваш рабочий набор ... даже если вы тянете среднюю страницу набора результатов, вы все равно можете сузить результат
  • Эти идеи работают из-за поведения оптимизатора запросов ... пакет обновления / исправление может изменить поведение (хотя, вероятно, нет).
  • В некоторых случаях SET ROWCOUNT может быть немного точным
  • Эта стратегия не учитывает получение полного количества строк, а делает пейджинг более эффективным

Так что же делать разработчику?

Читай мой хороший человек, читай. Вот несколько статей, на которые я опирался ...

Надеюсь, это поможет.

36 голосов
/ 24 июня 2009

Проверьте агрегат COUNT (*) при использовании с OVER (PARTITON BY ..), например:

    SELECT
     ROW_NUMBER() OVER(ORDER BY object_id, column_id) as RowNum
    , COUNT(*) OVER(PARTITION BY 1) as TotalRows
    , * 
    FROM master.sys.columns

Это ИМХО лучший способ сделать это без необходимости делать два запроса.

4 голосов
/ 24 июня 2009

Если счетчик (*) медленный, вам действительно нужно сначала решить эту проблему, внимательно изучив ваши индексы и убедившись, что ваша статистика актуальна.

По моему опыту, нет ничего лучше, чем выполнять два отдельных запроса: один для получения страницы данных и один для получения общего количества. Использование временной таблицы для подсчета общего количества - стратегия проигрышная, так как количество строк увеличивается. Например, стоимость вставки 10 000 000 миллионов строк во временную таблицу просто для их подсчета, очевидно, будет чрезмерной.

0 голосов
/ 24 июня 2009

Я делаю это, помещая весь набор результатов с row_number во временную таблицу, затем использую @@ rowcount из этого и использую запрос для этого, чтобы вернуть страницу данных, которые мне нужны.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...