Количество (*) против Количество (1) - SQL Server - PullRequest
675 голосов
/ 03 августа 2009

Просто интересно, если кто-нибудь из вас, ребята, использует Count(1) над Count(*) и есть ли заметная разница в производительности или это обычная привычка, появившаяся в прошлом?

(Конкретная база данных SQL Server 2005.)

Ответы [ 12 ]

562 голосов
/ 03 августа 2009

Разницы нет.

Причина:

Книги в Интернете говорит "COUNT ( { [ [ ALL | DISTINCT ] expression ] | * } )"

«1» - это ненулевое выражение, поэтому оно совпадает с COUNT(*). Оптимизатор распознает его таким, какой он есть: тривиальный.

То же, что EXISTS (SELECT * ... или EXISTS (SELECT 1 ...

Пример:

SELECT COUNT(1) FROM dbo.tab800krows
SELECT COUNT(1),FKID FROM dbo.tab800krows GROUP BY FKID

SELECT COUNT(*) FROM dbo.tab800krows
SELECT COUNT(*),FKID FROM dbo.tab800krows GROUP BY FKID

Тот же IO, тот же план, работы

Редактировать, август 2011

Аналогичный вопрос по DBA.SE .

Редактировать, декабрь 2011

COUNT(*) упоминается конкретно в ANSI-92 (ищите "Scalar expressions 125")

Корпус:

a) Если указано COUNT (*), то результатом будет количество элементов T.

То есть стандарт ANSI признает, что это очевидно, что вы имеете в виду. COUNT(1) было оптимизировано поставщиками РСУБД из-за этого суеверия. В противном случае это будет оцениваться согласно ANSI

b) В противном случае, пусть TX будет таблица из одного столбца, которая является результат применения <выражения значения> к каждой строке T и устранение нулевых значений. Если одно или несколько нулевых значений устраняется, затем возникает условие завершения: предупреждение-

70 голосов
/ 03 августа 2009

В SQL Server эти операторы дают одинаковые планы.

Вопреки распространенному мнению, в Oracle они тоже.

SYS_GUID() в Oracle - довольно трудоемкая функция.

В моей тестовой базе данных t_even - это таблица с 1,000,000 строками

Этот запрос:

SELECT  COUNT(SYS_GUID())
FROM    t_even

выполняется в течение 48 секунд, поскольку функция должна оценивать каждый возвращенный SYS_GUID(), чтобы убедиться, что это не NULL.

Однако этот запрос:

SELECT  COUNT(*)
FROM    (
        SELECT  SYS_GUID()
        FROM    t_even
        )

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

58 голосов
/ 03 августа 2009

Очевидно, что COUNT (*) и COUNT (1) будут всегда возвращать один и тот же результат. Поэтому, если бы один был медленнее другого, это было бы эффективно из-за ошибки оптимизатора. Поскольку обе формы очень часто используются в запросах, для СУБД не имеет смысла оставлять такую ​​ошибку незафиксированной. Следовательно, вы обнаружите, что производительность обеих форм (вероятно) одинакова во всех основных СУБД SQL.

20 голосов
/ 13 мая 2011

В стандарте SQL-92 COUNT(*) специально означает «мощность выражения таблицы» (это может быть базовая таблица, `VIEW, производная таблица, CTE и т. Д.).

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

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

Кроме того, в особом случае SELECT COUNT(*) FROM MyPersistedTable; предполагается, что СУБД может хранить статистические данные о количестве элементов таблицы.

Поэтому, поскольку COUNT(1) и COUNT(*) семантически эквивалентны, я использую COUNT(*).

16 голосов
/ 03 августа 2009

COUNT(*) и COUNT(1) одинаковы для результата и производительности.

12 голосов
/ 03 августа 2009

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

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

Тем не менее, я всегда использовал COUNT(*).

8 голосов
/ 20 августа 2016

Я провел быстрый тест на SQL Server 2012 на блоке hyper-v 8 ГБ ОЗУ. Вы можете увидеть результаты для себя. Во время выполнения этих тестов я не запускал никаких других оконных приложений, кроме SQL Server Management Studio.

Схема моей таблицы:

CREATE TABLE [dbo].[employee](
    [Id] [bigint] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](50) NOT NULL,
 CONSTRAINT [PK_employee] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

Общее количество записей в таблице Employee: 178090131 (~ 178 миллионов строк)

Первый запрос:

Set Statistics Time On
Go    
Select Count(*) From Employee
Go    
Set Statistics Time Off
Go

Результат первого запроса:

 SQL Server parse and compile time: 
 CPU time = 0 ms, elapsed time = 35 ms.

 (1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 10766 ms,  elapsed time = 70265 ms.
 SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

Второй запрос:

    Set Statistics Time On
    Go    
    Select Count(1) From Employee
    Go    
    Set Statistics Time Off
    Go

Результат второго запроса:

 SQL Server parse and compile time: 
   CPU time = 14 ms, elapsed time = 14 ms.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 11031 ms,  elapsed time = 70182 ms.
 SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

Вы можете заметить, что существует разница в 83 (= 70265 - 70182) миллисекунды, которую легко можно отнести к точному состоянию системы во время выполнения запросов. Также я сделал один прогон, так что эта разница станет более точной, если я сделаю несколько прогонов и сделаю некоторое усреднение. Если для такого огромного набора данных разница составляет менее 100 миллисекунд, то мы можем легко заключить, что два запроса не имеют разницы в производительности, демонстрируемой механизмом SQL Server.

Примечание : ОЗУ достигает почти 100% использования в обоих прогонах. Я перезапустил службу SQL Server перед началом обоих запусков.

8 голосов
/ 05 мая 2014

Поскольку этот вопрос возникает снова и снова, вот еще один ответ. Я надеюсь добавить кое-что новичкам, интересующимся «лучшей практикой» здесь.

SELECT COUNT(*) FROM something считает записи, что является легкой задачей.

SELECT COUNT(1) FROM something извлекает 1 для каждой записи, а затем подсчитывает ненулевые 1, что, в сущности, означает подсчет записей, только сложнее.

Сказав это: Хорошие БД замечают, что второе утверждение приведет к тому же счету, что и первое утверждение, и соответственно интерпретируют его, чтобы не выполнять ненужную работу. Поэтому обычно оба оператора приводят к одному и тому же плану выполнения и занимают одинаковое количество времени.

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

7 голосов
/ 15 января 2019

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

Во-первых, семантической разницы между select count(1) from table и select count(*) from table нет. Они возвращают одинаковые результаты во всех случаях (и это ошибка, если нет). Как отмечено в других ответах, select count(column) from table семантически отличается и не всегда возвращает те же результаты, что и count(*).

Во-вторых, в отношении производительности в SQL Server (и в SQL Azure) могут иметь значение два аспекта: работа во время компиляции и работа во время выполнения. Работа во время компиляции - это незначительный объем дополнительной работы в текущей реализации. В некоторых случаях расширение * распространяется на все столбцы, после чего выводится обратно до 1 столбца, выводимого из-за того, как некоторые из внутренних операций работают при связывании и оптимизации. Я сомневаюсь, что это проявится в любом измеримом тесте и, вероятно, затеряется в шуме всех других вещей, которые происходят под прикрытием (таких как автостатистика, сеансы xevent, накладные расходы хранилища запросов, триггеры и т. Д.). Это может быть несколько тысяч дополнительных инструкций процессора. Таким образом, count (1) выполняет чуть меньше работы во время компиляции (что обычно происходит один раз, и план кэшируется в нескольких последующих выполнениях). Что касается времени выполнения, при условии, что планы одинаковы, не должно быть никакой измеримой разницы. (Один из предыдущих примеров показывает разницу - скорее всего, из-за других факторов на машине, если план такой же).

Относительно того, как план может быть другим. Это крайне маловероятно, но это потенциально возможно в архитектуре текущего оптимизатора. Оптимизатор SQL Server работает как поисковая программа (представьте: компьютерная программа, играющая в шахматы, ищущая различные альтернативы для разных частей запроса и оценивающая альтернативы, чтобы найти самый дешевый план за разумное время). Этот поиск имеет несколько ограничений на то, как он работает, чтобы компиляция запросов заканчивалась в разумные сроки. Для запросов, выходящих за рамки тривиальных, существуют фазы поиска, и они имеют дело с траншами запросов, основанными на том, насколько дорогостоящим оптимизатор считает, что запрос потенциально может выполняться. Существует 3 основных этапа поиска, и каждый этап может использовать более агрессивную (дорогую) эвристику, пытаясь найти более дешевый план, чем любое предыдущее решение. В конце концов, в конце каждой фазы происходит процесс принятия решения, который пытается определить, должен ли он вернуть план, который он нашел до сих пор, или должен продолжать поиск. Этот процесс использует общее время, затраченное на данный момент, в сравнении с оценочной стоимостью лучшего плана, найденного до сих пор. Таким образом, на разных машинах с разными скоростями ЦП возможно (хотя и редко) получить разные планы из-за тайм-аута на более ранней стадии с планом по сравнению с переходом на следующую фазу поиска. Есть также несколько похожих сценариев, связанных с тайм-аутом последней фазы и, возможно, нехваткой памяти для очень и очень дорогих запросов, которые занимают всю память на машине (обычно это не проблема для 64-битных систем, но это была большая проблема назад на 32-битных серверах). В конечном итоге, если вы получите другой план, производительность во время выполнения будет отличаться. Я не думаю, что маловероятно, что разница во времени компиляции НИКОГДА приведет к возникновению любого из этих условий.

Net-net: Пожалуйста, используйте любой из двух вариантов, который вам нужен, поскольку в практической форме ничего из этого не имеет значения. (Честно говоря, существует гораздо более значительный фактор, влияющий на производительность в SQL).

Надеюсь, это поможет. Я написал главу книги о том, как работает оптимизатор, но я не знаю, уместно ли размещать его здесь (так как я все еще верю, что я получаю от него небольшие гонорары). Таким образом, вместо публикации я опубликую ссылку на выступление, которое я дал на SQLBits в Великобритании о том, как оптимизатор работает на высоком уровне, чтобы вы могли видеть различные основные фазы поиска более подробно, если хотите. чтобы узнать об этом. Вот ссылка на видео: https://sqlbits.com/Sessions/Event6/inside_the_sql_server_query_optimizer

7 голосов
/ 04 февраля 2010
SET STATISTICS TIME ON

select count(1) from MyTable (nolock) -- table containing 1 million records. 

Время выполнения SQL Server:
Время процессора = 31 мс, прошедшее время = 36 мс.

select count(*) from MyTable (nolock) -- table containing 1 million records. 

Время выполнения SQL Server:
Время ЦП = 46 мс, прошедшее время = 37 мс.

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

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