Действительно ли count (*) действительно дорог? - PullRequest
18 голосов
/ 27 апреля 2010

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

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

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

Сколько стоят запросы COUNT (*)?

Ответы [ 7 ]

8 голосов
/ 27 апреля 2010

Как правило, стоимость 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(*) будет пропорционально количеству совпадений, поскольку поиск по индексу будет выполнен после каждый матч.

7 голосов
/ 27 апреля 2010

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

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

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

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

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

2 голосов
/ 27 апреля 2010

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

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

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

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

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

1 голос
/ 27 апреля 2010

Если страница становится медленной, вы можете обратить внимание на то, как минимизируется количество обращений к базе данных, если это вообще возможно. Даже если ваши 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, должен быть способ включить автоматическую пакетную обработку запросов.

0 голосов
/ 27 апреля 2010

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

0 голосов
/ 27 апреля 2010

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

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

0 голосов
/ 27 апреля 2010

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

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

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

...