Действительно ли count(*) дорогой?

У меня есть страница, где у меня есть 4 вкладки, отображающие 4 разных отчета на основе разных таблиц.

Я получаю количество строк каждой таблицы, используя select count(*) from <table> запросить и отобразить количество строк, доступных в каждой таблице на вкладках. В результате каждый постбэк вызывает 5 count(*) Запросы для выполнения (4 для получения количества и 1 для разбивки на страницы) и 1 запрос для получения содержимого отчета.

Теперь мой вопрос: count(*) запросы действительно дорогие - нужно ли сохранять количество строк (по крайней мере, тех, которые отображаются на вкладке) в состоянии просмотра страницы вместо нескольких запросов?

Насколько дороги запросы COUNT(*)?

7 ответов

Решение

Вам нужно подключить SQL Profiler или профилировщик уровня приложения, например L2SProf, и посмотреть на реальные затраты на запрос в вашем контексте, прежде чем:

  • угадывая, в чем проблема, и пытаясь определить вероятные выгоды потенциального решения

  • позволяя другим угадывать для вас на да паутинах - есть много дезинформации без цитат, в том числе в этой теме (но не в этом посте:P)

Когда вы это сделаете, станет ясно, каков наилучший подход, т. Е. Доминирует ли SELECT COUNT или нет и т. Д.

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

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

В простых случаях, когда вы имеете дело с одной таблицей, часто существуют специальные оптимизации, чтобы сделать такую ​​операцию дешевой. Например, делать COUNT(*) без WHERE условия от одного MyISAM стол в MySQL - это происходит мгновенно, так как хранится в метаданных.

Например, давайте рассмотрим два запроса:

SELECT  COUNT(*)
FROM    largeTableA a

Поскольку каждая запись удовлетворяет запросу, COUNT(*) стоимость пропорциональна количеству записей в таблице (т. е. пропорциональна тому, что она возвращает) (при условии, что ей нужно посетить строки, и для их обработки не существует специальной оптимизации)

SELECT  COUNT(*)
FROM    largeTableA a
JOIN    largeTableB b
ON      a.id = b.id

В этом случае двигатель, скорее всего, будет использовать HASH JOIN и план выполнения будет примерно таким:

  1. Построить хеш-таблицу на меньшем из столов
  2. Сканирование таблицы большего размера, поиск каждой записи в хэш-таблице
  3. Посчитай спички, как они идут.

В этом случае COUNT(*) издержки (шаг 3) будут незначительными, и время запроса будет полностью определено шагами 1 и 2, то есть построением хеш-таблицы и ее поиском. Для такого запроса время будет O(a + b): это действительно не зависит от количества матчей.

Однако, если есть индексы на обоих a.id а также b.id, MERGE JOIN может быть выбран и COUNT(*) время снова будет пропорционально количеству совпадений, так как поиск индекса будет выполняться после каждого совпадения.

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

Если вы оцените и определите, что стоимость незначительна, у вас (в настоящее время) нет проблем.

Если он окажется слишком дорогим для вашего сценария, вы можете сделать свою нумерацию страниц "нечеткой", как в "Отображение 1–500 из примерно 30000", используя

SELECT rows FROM sysindexes WHERE id = OBJECT_ID('sometable') AND indid < 2

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

Если страница становится медленной, вы можете обратить внимание на то, как можно минимизировать количество обращений к базе данных, если это вообще возможно. Даже если ваш COUNT(*) запросы O(1), если вы делаете их достаточно, это, безусловно, может замедлить работу.

Вместо настройки и выполнения 5 отдельных запросов по одному, запустите SELECT операторы в одной партии и обработать 5 результатов одновременно.

Т.е., если вы используете ADO.NET, сделайте что-то вроде этого (проверка краткости не включена, для ясности не зациклена / не динамична):

string sql = "SELECT COUNT(*) FROM Table1; SELECT COUNT(*) FROM Table2;"

SqlCommand cmd = new SqlCommand(sql, connection);
SqlDataReader dr = cmd.ExecuteReader();

// Defaults to first result set
dr.Read();
int table1Count = (int)dr[0];

// Move to second result set
dr.NextResult();
dr.Read();
int table2Count = (int)dr[0];

Если вы используете ORM некоторого вида, например, NHibernate, должен быть способ включить автоматическую пакетную обработку запросов.

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

Судя по всему, вы каждый раз вызываете операцию загрузки таблицы, которая является медленной, но если она не выполняется заметно медленно или не вызывает каких-либо проблем, не оптимизируйте: преждевременная и ненужная оптимизация может привести к значительным беда!

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

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

Вы упоминаете о сохранении счетчиков в состоянии просмотра, безусловно, вариант, если поведение кода приемлемо, когда этот счетчик неверен, потому что базовые записи исчезли или были добавлены.

Это зависит от того, что вы делаете с данными в этой таблице. Если они меняются очень часто и вам нужны они каждый раз, возможно, вы могли бы сделать триггер, который заполнит другую таблицу, которая состоит только из подсчетов из этой таблицы. Если вам нужно показать эти данные отдельно, возможно, вы могли бы просто выполнить "select count(*)..." только для одной конкретной таблицы. Это сразу пришло мне в голову, но я уверен, что есть и другие способы ускорить это. Кеш данных, может быть?:)

Другие вопросы по тегам