Количество (*) против Количество (1) - SQL Server


738

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

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


7
Не знаю о SQL Server, но в MySQL нет никакой разницы. COUNT (столбец), с другой стороны, отличается
— Грег

118
Не правда. COUNT (SomeColumn) будет возвращать только количество строк, которые содержат ненулевые значения для SomeColumn. COUNT (*) и COUNT ('Foo') будут возвращать общее количество строк в таблице.
— Стив Броберг

1
для получения более подробной информации проверьте этот выбор счетчика 1 против выбора счетчика * подробно с графиком
— Али Adravi

4
Ух ты, Стив, и вот я уже 5 лет в TSQL, не зная count (*) vs Count (ColumnName). Спасибо
— Хариндака

3
Обратите внимание также ответы на COUNT(*)против COUNT(1)против COUNT(pk)- что лучше? , Там тоже COUNT(*)против COUNT(column-name)- что правильнее? , Там вполне могут быть другие дубликаты.
— Джонатан Леффлер

Ответы:


598

Нет никакой разницы.

Причина:

Книги он-лайн говорит " 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 и исключения нулевых значений. Если одно или несколько нулевых значений удаляются, то возникает условие завершения: warning-


73

В 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(*))


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

@asgs: почему ты так думаешь? Как COUNT(*)зависит от значений SYS_GUID?
— Quassnoi

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

1
@asgs: предполагая, что вы знаете, что делает mapметод, вы видите, как эти два выражения: t_even.map(() => sys_guid()).lengthи t_even.lengthвсегда будут возвращать одно и то же значение? Оптимизатор Oracle достаточно умен, чтобы увидеть и оптимизировать mapчасть.
— Quassnoi

1
@asgs точно. Небольшое исправление: lengthне совсем зависит от того, из чего состоит коллекция, только от количества ее элементов. Если это число хранится в метаданных коллекции (это не относится к Oracle или большинству других современных СУБД, но относится к старому механизму хранения MySQL, MyISAM), тогда COUNT(*)просто нужно взять значение из метаданных.
— Quassnoi

65

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


Я бы не считал это ошибкой, если count (1) был медленнее, чем count (*). Если вы попросите dbms сгенерировать 1 с и посчитать те, которые не равны NULL, тогда да, это сводится к количеству записей, но вы не можете ожидать, что dbms обнаружит каждую глупость, которую вы напишете, и обойдет ее для вас.
— Торстен Кеттнер

1
Что ж, оптимизатор предназначен для оптимизации, и для подсчета нужно учитывать только 2 случая: выражение, которое может быть нулевым, выражение, которое никогда не будет нулевым: count (1) относится к последнему, поэтому СУБД не нужно "сгенерировать" 1, чтобы ответить на вопрос. (Кстати, я бы никогда не использовал ничего, кроме count (*), только по эстетическим соображениям.)
— Тони Эндрюс

46

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

Во- первых, не существует семантическая разница между select count(1) from tableVS. 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


2
я считаю, что 1также подвергается такой же экспансии. Я основываю это на тестах производительности здесь stackoverflow.com/questions/1597442/… также вижу пример в этом ответе на запрос, использующий 1неожиданный сбой, когда разрешения на уровне столбца находятся в игре
— Martin Smith,

21

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

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

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

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

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


1
Текст SQL-92, связанный с моим ответом на DBA.SE: dba.stackexchange.com/questions/2511/…
— gbn


12

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

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

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


Согласно принятому ответу, это не так для MS SQL - на самом деле нет никакой разницы между ними.
— Дэвид Мангейм

10

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

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

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

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

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


8

Я провел быстрый тест на 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 перед запуском обоих прогонов.


7
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(*)имеют большее время процессора.


14
Я не могу воспроизвести это. count(*)и count(1)возвращать результаты в течение нескольких мс друг от друга, даже при подсчете таблицы с 4,5 миллионами строк, в моем экземпляре SQL 2008.
— Джефф Этвуд

2
Иногда в некоторых системах оператор сначала выполняется всегда выполняется быстрее ... Вы рандомизировали порядок, в котором они выполняются?
— JosephDoggie

@JosephDoggie, всегда нужно перезапускать службу SQL Server перед выполнением каждого запроса при проведении таких измерений / статистики. Когда вы только что запустили службу SQL Server, каждый запуск становится полностью независимым, и порядок запросов не должен иметь значения. С другой стороны, если вы не перезапустите службу SQL Server и ядро ​​выполнит какое-то кэширование планов выполнения, тогда выполняемый позже запрос должен выполняться быстрее, чем первый.
— RBT

Время выполнения должно учитывать точные планы запросов при выполнении сравнений. Если они отличаются (скажем, агрегат хешей от сортировки + агрегат потоков), то результаты не сопоставимы. Поэтому я призываю к осторожности, делая выводы здесь без дополнительных данных.
— Конор Каннингем MSFT

3

Существует статья показывает , что COUNT(1)на Oracle это просто псевдоним COUNT(*), с доказательством об этом.

Я процитирую некоторые части:

Существует часть программного обеспечения базы данных, которое называется «Оптимизатор», которое определено в официальной документации как «Встроенное программное обеспечение базы данных, которое определяет наиболее эффективный способ выполнения оператора SQL».

Один из компонентов оптимизатора называется «преобразователем», роль которого состоит в том, чтобы определить, выгодно ли переписать исходный оператор SQL в семантически эквивалентный оператор SQL, который мог бы быть более эффективным.

Хотели бы вы увидеть, что делает оптимизатор, когда вы пишете запрос с помощью COUNT (1)?

С пользователями с ALTER SESSIONпривилегией, вы можете положить tracefile_identifier, включите трассировку оптимизатора и запуск COUNT(1)выберите, как: SELECT /* test-1 */ COUNT(1) FROM employees;.

После этого вам нужно локализовать файлы трассировки, что можно сделать SELECT VALUE FROM V$DIAG_INFO WHERE NAME = 'Diag Trace';. Позже в файле вы найдете:

SELECT COUNT(*) “COUNT(1)” FROM “COURSE”.”EMPLOYEES” “EMPLOYEES”

Как видите, это просто псевдоним для COUNT(*).

Еще один важный комментарий: два десятилетия назад в Oracle COUNT(*)было намного быстрее , чем в Oracle 7.3:

Count (1) был переписан в count (*) начиная с 7.3, потому что Oracle любит автоматически настраивать мифические утверждения. В более ранней версии Oracle7 оракул должен был оценивать (1) для каждой строки как функцию, прежде чем существуют ДЕТЕРМИНИСТИЧЕСКИЕ и НЕДЕТЕРМИНИСТИЧЕСКИЕ.

Итак, два десятилетия назад count (*) был быстрее

Для других баз данных, таких как Sql Server, их следует исследовать отдельно для каждой.

Я знаю, что этот вопрос специфичен для Sql Server, но другие вопросы по SO по той же теме, не говоря уже о базе данных, были закрыты и помечены как дублированные из этого ответа.


1

Во всех СУБД оба способа подсчета эквивалентны с точки зрения того, какой результат они производят. Что касается производительности, я не заметил каких-либо различий в производительности в SQL Server, но, возможно, стоит отметить, что некоторые СУБД, например PostgreSQL 11, имеют менее оптимальные реализации, COUNT(1)поскольку они проверяют обнуляемость выражения аргумента, как можно увидеть в этом посте .

Я обнаружил, что при работе с 1М строк разница в производительности составляет 10%:

-- Faster
SELECT COUNT(*) FROM t;

-- 10% slower
SELECT COUNT(1) FROM t;

0

COUNT (1) существенно не отличается от COUNT (*), если вообще. Что касается вопроса о СЧЕТАХ НЕДОПУСТИМЫХ КОЛОННАХ, это может быть просто продемонстрировать разницу между COUNT (*) и COUNT (<some col>) -

USE tempdb;
GO

IF OBJECT_ID( N'dbo.Blitzen', N'U') IS NOT NULL DROP TABLE dbo.Blitzen;
GO

CREATE TABLE dbo.Blitzen (ID INT NULL, Somelala CHAR(1) NULL);

INSERT dbo.Blitzen SELECT 1, 'A';
INSERT dbo.Blitzen SELECT NULL, NULL;
INSERT dbo.Blitzen SELECT NULL, 'A';
INSERT dbo.Blitzen SELECT 1, NULL;

SELECT COUNT(*), COUNT(1), COUNT(ID), COUNT(Somelala) FROM dbo.Blitzen;
GO

DROP TABLE dbo.Blitzen;
GO
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.