Количество (*) против Количество (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.