Должен ли я считать (*) или нет? - PullRequest
72 голосов
/ 19 января 2009

Я знаю, что вообще делать такие запросы - это плохая идея:

SELECT * FROM `group_relations`

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

SELECT COUNT(*) FROM `group_relations`

Или более конкретный

SELECT COUNT(`group_id`) FROM `group_relations`

У меня есть ощущение, что последнее потенциально может быть быстрее, но есть ли еще что-то, что стоит рассмотреть?

Обновление : в этом случае я использую InnoDB, извините, что не уточнил.

Ответы [ 14 ]

100 голосов
/ 19 января 2009

Если рассматриваемый столбец НЕ НЕДЕЙСТВИТЕЛЕН, оба ваших запроса эквивалентны. Когда group_id содержит нулевые значения,

select count(*)

будет считать все строки, тогда как

select count(group_id)

будет считать только те строки, в которых идентификатор_группы не равен нулю.

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

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

22 голосов
/ 19 января 2009

Если я правильно помню, в MYSQL COUNT (*) считает все строки, тогда как COUNT (column_name) считает только те строки, которые имеют значение, отличное от NULL, в данном столбце.

11 голосов
/ 19 января 2009

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

Важно отметить в MySQL:

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

8 голосов
/ 19 января 2009

если вы попытаетесь SELECT COUNT(1) FROM group_relations, это будет немного быстрее, потому что он не будет пытаться получить информацию из ваших столбцов.

Редактировать: Я только что провел некоторое исследование и обнаружил, что это происходит только в некоторых БД. В sqlserver это то же самое, что использовать 1 или *, но в Oracle это быстрее, чем 1.

http://social.msdn.microsoft.com/forums/en-US/transactsql/thread/9367c580-087a-4fc1-bf88-91a51a4ee018/

Очевидно, что в mysql нет никакой разницы между ними, как и в sqlserver, парсер, по-видимому, изменяет запрос на select (1). Извините, если я вас каким-то образом ввел в заблуждение.

5 голосов
/ 20 марта 2009

Мне самому было любопытно. Хорошо читать документацию и теоретические ответы, но мне нравится сопоставлять их с эмпирическими данными.

У меня есть таблица MySQL (InnoDB), в которой содержится 5 607 997 записей. Таблица находится в моей собственной изолированной программной среде, поэтому я знаю, что содержимое статично, и никто другой не использует сервер. Я думаю, что это эффективно удаляет все внешние воздействия на производительность. У меня есть таблица с полем первичного ключа auto_increment (Id), которое, как я знаю, никогда не будет равно нулю, и я буду использовать его для проверки условия where (WHERE Id NOT NULL).

Единственный другой возможный сбой, который я вижу при запуске тестов, - это кеш. Первый запуск запроса всегда будет медленнее, чем последующие запросы, использующие те же индексы. Я буду называть это ниже вызовом Seeding в кэше. Просто чтобы немного его перепутать, я запустил его с предложением where, которое, как я знаю, всегда будет иметь значение true, независимо от каких-либо данных (TRUE = TRUE).

Здесь сказано, что мои результаты:

QueryType

      |  w/o WHERE          | where id is not null |  where true=true

COUNT ()

      |  9 min 30.13 sec ++ | 6 min 16.68 sec ++   | 2 min 21.80 sec ++
      |  6 min 13.34 sec    | 1 min 36.02 sec      | 2 min 0.11 sec 
      |  6 min 10.06 se     | 1 min 33.47 sec      | 1 min 50.54 sec

COUNT (Id)

      |  5 min 59.87 sec    | 1 min 34.47 sec      | 2 min 3.96 sec 
      |  5 min 44.95 sec    | 1 min 13.09 sec      | 2 min 6.48 sec

COUNT (1)

      | 6 min 49.64 sec    | 2 min 0.80 sec       | 2 min 11.64 sec
      | 6 min 31.64 sec    | 1 min 41.19 sec      | 1 min 43.51 sec

++ Это считается вызовом заполнения кэша. Ожидается, что он будет медленнее, чем остальные.

Я бы сказал, что результаты говорят сами за себя. COUNT (Id) обычно вытесняет других. Добавление предложения Where значительно сокращает время доступа, даже если это предложение, которое, как вы знаете, оценивает как истинное. Похоже, что сладкой точкой является COUNT (Id) ... ГДЕ Id НЕ НЕДЕЙСТВИТЕЛЕН.

Мне бы очень хотелось увидеть результаты других людей, возможно, с меньшими таблицами или с предложениями where для полей, отличных от того, на которое вы рассчитываете. Я уверен, что есть другие варианты, которые я не учел.

4 голосов
/ 08 июля 2009

Поиск альтернатив

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

Зная, что COUNT запросы будут расти медленно, вы могли бы рассмотреть альтернативный способ отображения элементов управления разбиением на страницы, который просто позволяет обойти медленный запрос. Нумерация страниц Google - отличный пример.

Денормализовать

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

Если вы решили сделать это, рассмотрите возможность использования идемпотентных транзакционных операций для синхронизации этих денормализованных значений.

BEGIN TRANSACTION;
INSERT INTO  `group_relations` (`group_id`) VALUES (1);
UPDATE `group_relations_count` SET `count` = `count` + 1;
COMMIT;

Кроме того, вы можете использовать триггеры базы данных, если ваша СУБД поддерживает их.

В зависимости от вашей архитектуры, возможно, имеет смысл использовать слой кэширования, такой как memcached, для хранения, увеличения и уменьшения денормализованного значения и просто перейти к медленному запросу COUNT, если ключ кеша отсутствует. Это может уменьшить общее количество конфликтов при записи, если у вас есть очень изменчивые данные, хотя в подобных случаях вы захотите рассмотреть решения для эффекта собачьей кучи .

2 голосов
/ 23 июня 2009

Лучше всего считать по индексированному столбцу, такому как первичный ключ.

SELECT COUNT(`group_id`) FROM `group_relations`
2 голосов
/ 09 марта 2009

COUNT (*) факты и мифы:

МИФ : «InnoDB плохо обрабатывает количество (*) запросов»:

Большинство запросов count (*) выполняются всеми механизмами хранения одинаково, если у вас есть предложение WHERE, в противном случае InnoDB придется выполнить полное сканирование таблицы.

FACT : InnoDB не оптимизирует количество (*) запросов без предложения where

2 голосов
/ 19 января 2009

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

.

Я интуитивно понимаю, что выбрать COUNT (*) быстрее, чем выбрать COUNT (поле). Если СУБД обнаружила, что вы указали «*» в СЧЕТЕ вместо поля, ей не нужно ничего оценивать для увеличения счетчика. Принимая во внимание, что если вы укажете поле в COUNT, СУБД всегда будет определять, является ли ваше поле пустым или нет, чтобы считать его.

Но если ваше поле можно обнулять, укажите это поле в COUNT.

2 голосов
/ 19 января 2009

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

...