2009-10-20 2 views
73

Я использовал, чтобы написать мой EXISTS проверяет так:подзапросов с использованием Exists 1 или Exists *

IF EXISTS (SELECT * FROM TABLE WHERE [email protected]) 
BEGIN 
    UPDATE TABLE SET ColumnsX=ValuesX WHERE Where [email protected] 
END 

Один из АБД в предыдущей жизни, сказал мне, что когда я делаю EXISTS пункт, используйте SELECT 1 вместо SELECT *

IF EXISTS (SELECT 1 FROM TABLE WHERE [email protected]) 
BEGIN 
    UPDATE TABLE SET ColumnsX=ValuesX WHERE [email protected] 
END 

Действительно ли это имеет значение?

+1

Вы забыли EXISTS (SELECT NULL FROM ...). Это было задано недавно btw –

+13

p.s. получить новый администратор базы данных. Суеверие не имеет места в ИТ, особенно в управлении базами данных (от прежнего DBA !!!) –

ответ

110

№ Это было покрыто bazillion раз. SQL Server является интеллектуальным и знает, что он используется для EXISTS и возвращает NO DATA в систему.

Quoth Microsoft: http://technet.microsoft.com/en-us/library/ms189259.aspx?ppud=4

Список выбора подзапроса введенного СУЩЕСТВУЕТ почти всегда состоит из звездочки (*). Существует нет причины перечислять имена столбцов, потому что вы просто проверяете, существуют ли строки, которые соответствуют условиям, указанным в подзапросе .

Кроме того, не верьте мне? Попробуйте выполнить следующие задачи:

SELECT whatever 
    FROM yourtable 
WHERE EXISTS(SELECT 1/0 
       FROM someothertable 
       WHERE a_valid_clause) 

Если это действительно что-то делает с SELECT-списком, это приведет к ошибке div на нуль. Это не так.

EDIT: Обратите внимание, что SQL-стандарт фактически говорит об этом.

ANSI SQL 1992 Standard, стр 191 http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt

3) Корпус:
а) Если <select list> "*" просто содержится в <subquery>, что немедленно содержится в <exists predicate>, то <select list> составляет , эквивалентный <value expression> , который является произвольным <literal>.

+56

+1 для 1/0 в разделе EXISTS. – gbn

+1

трюк 'EXISTS' с 1/0 может быть даже расширен до этого' SELECT 1 WHERE EXISTS (SELECT 1/0) '... кажется шагом более абстрактным, тогда как второй' SELECT' не имеет предложения 'FROM' – whytheq

+1

@whytheq - или 'SELECT COUNT (*) ГДЕ СУЩЕСТВУЕТ (SELECT 1/0)'. «SELECT» без «FROM» в SQL Server обрабатывается так, как если бы он обращался к одной таблице строк (например,аналогично выбору из таблицы «dual» в других РСУБД) –

7

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

+2

+1. Не знаю, почему это было отклонено. Я всегда думал, что лучше научить человека ловить рыбу, чем просто дать ему рыбу. Как люди узнают что-нибудь? –

+1

+1 Сделай сам совет всегда приветствуется. –

-1

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

1

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

5

В SQL Server нет разницы, и это никогда не было проблемой в SQL Server. Оптимизатор знает, что они одинаковы. Если вы посмотрите планы выполнения, вы увидите, что они идентичны.

89

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

CREATE TABLE T 
(
X INT PRIMARY KEY, 
Y INT, 
Z CHAR(8000) 
) 

CREATE NONCLUSTERED INDEX NarrowIndex ON T(Y) 

IF EXISTS (SELECT * FROM T) 
    PRINT 'Y' 

дает Планируйте

Plan

Это показывает, что SQL Server был в состоянии использовать узкий индекс доступной для проверки результата, несмотря на то, что индекс не включает в себя все столбцы. Доступ к индексу находится под оператором semi join, что означает, что он может остановить сканирование сразу после возвращения первой строки.

Так что ясно, что вышеуказанное убеждение неверно.

Однако Конор Cunningham из команды Query Оптимизатор объясняет here, что он обычно использует SELECT 1 в этом случае, как это может сделать незначительную разницу в производительности в компиляции запроса.

QP будет принимать и развернуть все * «ы в начале трубопровода и привязать их к объектов (в данном случае, список столбцов). Затем он удалит ненужных столбцов из-за характера запроса .

Таким образом, для простого EXISTS подзапроса как этого:

SELECT col1 FROM MyTable WHERE EXISTS (SELECT * FROM Table2 WHERE MyTable.col1=Table2.col2)* будет расширено до некоторого потенциально большого списка колонка, а затем он будет установлено, что семантика EXISTS не требует какой-либо из них колонок, поэтому в основном все они могут удаляться .

«SELECT 1» будет избегать использования для просмотра любых ненужных метаданных для этой таблицы во время компиляции запроса.

Однако во время выполнения две формы: запрос будет идентичным и будет иметь идентичное время автономной работы.

Я проверил четыре возможных способа выражения этого запроса на пустой таблице с различным количеством столбцов. SELECT 1 vs SELECT * vs SELECT Primary_Key vs SELECT Other_Not_Null_Column.

Я выполнил запросы в цикле, используя OPTION (RECOMPILE), и измерил среднее число выполнений в секунду. Результаты ниже

enter image description here

+-------------+----------+---------+---------+--------------+ 
| Num of Cols | *  | 1 | PK | Not Null col | 
+-------------+----------+---------+---------+--------------+ 
| 2   | 2043.5 | 2043.25 | 2073.5 | 2067.5  | 
| 4   | 2038.75 | 2041.25 | 2067.5 | 2067.5  | 
| 8   | 2015.75 | 2017 | 2059.75 | 2059   | 
| 16   | 2005.75 | 2005.25 | 2025.25 | 2035.75  | 
| 32   | 1963.25 | 1967.25 | 2001.25 | 1992.75  | 
| 64   | 1903  | 1904 | 1936.25 | 1939.75  | 
| 128   | 1778.75 | 1779.75 | 1799 | 1806.75  | 
| 256   | 1530.75 | 1526.5 | 1542.75 | 1541.25  | 
| 512   | 1195  | 1189.75 | 1203.75 | 1198.5  | 
| 1024  | 694.75 | 697  | 699  | 699.25  | 
+-------------+----------+---------+---------+--------------+ 
| Total  | 17169.25 | 17171 | 17408 | 17408  | 
+-------------+----------+---------+---------+--------------+ 

Как можно видеть, не соответствует победитель между SELECT 1 и SELECT * и разница между этими двумя подходами незначительна. Однако SELECT Not Null col и SELECT PK выглядят немного быстрее.

Все четыре запроса ухудшаются по мере увеличения количества столбцов в таблице.

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

SET SHOWPLAN_TEXT ON; 

GO 

SELECT COUNT(1) 
FROM master..spt_values 

Что дает следующий план

|--Compute Scalar(DEFINE:([Expr1003]=CONVERT_IMPLICIT(int,[Expr1004],0))) 
     |--Stream Aggregate(DEFINE:([Expr1004]=Count(*))) 
      |--Index Scan(OBJECT:([master].[dbo].[spt_values].[ix2_spt_values_nu_nc])) 

Прикрепление отладчик к процессу SQL Server и случайным образом нарушение во время выполнения ниже

DECLARE @V int 

WHILE (1=1) 
    SELECT @V=1 WHERE EXISTS (SELECT 1 FROM ##T) OPTION(RECOMPILE) 

я обнаружил, что в тех случаях, когда таблица имеет 1,024 столбца большую часть времени, когда стек вызовов выглядит примерно так, как показано ниже, что он действительно тратит большую часть времени на загрузку столбцов метаданных, даже п SELECT 1 используется (для случая, когда таблица имеет 1 столбец случайным образом ломать не попали эту битый стек вызовов в 10 попыток)

sqlservr.exe!CMEDAccess::GetProxyBaseIntnl() - 0x1e2c79 bytes 
sqlservr.exe!CMEDProxyRelation::GetColumn() + 0x57 bytes 
sqlservr.exe!CAlgTableMetadata::LoadColumns() + 0x256 bytes  
sqlservr.exe!CAlgTableMetadata::Bind() + 0x15c bytes 
sqlservr.exe!CRelOp_Get::BindTree() + 0x98 bytes 
sqlservr.exe!COptExpr::BindTree() + 0x58 bytes 
sqlservr.exe!CRelOp_FromList::BindTree() + 0x5c bytes 
sqlservr.exe!COptExpr::BindTree() + 0x58 bytes 
sqlservr.exe!CRelOp_QuerySpec::BindTree() + 0xbe bytes 
sqlservr.exe!COptExpr::BindTree() + 0x58 bytes 
sqlservr.exe!CScaOp_Exists::BindScalarTree() + 0x72 bytes 
... Lines omitted ... 
msvcr80.dll!_threadstartex(void * ptd=0x0031d888) Line 326 + 0x5 bytes C 
[email protected]() + 0x37 bytes 

Это руководство попытки профилирования подкрепленная коды профайлер VS 2012 который показывает очень различный выбор функций, потребляющих время компиляции для двух случаев (Top 15 Functions 1024 columns против Top 15 Functions 1 column).

Иконы SELECT 1 и SELECT * завершают проверку разрешений столбцов и не выполняются, если пользователю не предоставляется доступ ко всем столбцам в таблице.

Пример я списаны из разговора на the heap

CREATE USER blat WITHOUT LOGIN; 
GO 
CREATE TABLE dbo.T 
(
X INT PRIMARY KEY, 
Y INT, 
Z CHAR(8000) 
) 
GO 

GRANT SELECT ON dbo.T TO blat; 
DENY SELECT ON dbo.T(Z) TO blat; 
GO 
EXECUTE AS USER = 'blat'; 
GO 

SELECT 1 
WHERE EXISTS (SELECT 1 
       FROM T); 
/* ↑↑↑↑ 
Fails unexpectedly with 

The SELECT permission was denied on the column 'Z' of the 
      object 'T', database 'tempdb', schema 'dbo'.*/ 

GO 
REVERT; 
DROP USER blat 
DROP TABLE T 

Так что можно предположить, что незначительные очевидное различие при использовании SELECT some_not_null_col является то, что он только завершаться проверки разрешения на этом конкретном столбце (хотя по-прежнему загружает метаданные для всех). Однако это не похоже на факты в виде процентной разницы между этими двумя подходами, если что-то становится меньше по мере увеличения количества столбцов в базовой таблице.

В любом случае я не буду спешить и изменить все свои запросы к этой форме, так как разница очень незначительна и очевидна во время компиляции запроса. Удаление OPTION (RECOMPILE), чтобы последующие исполнения могли использовать кешированный план, дали следующее.

enter image description here

+-------------+-----------+------------+-----------+--------------+ 
| Num of Cols |  *  |  1  | PK  | Not Null col | 
+-------------+-----------+------------+-----------+--------------+ 
| 2   | 144933.25 | 145292  | 146029.25 | 143973.5  | 
| 4   | 146084 | 146633.5 | 146018.75 | 146581.25 | 
| 8   | 143145.25 | 144393.25 | 145723.5 | 144790.25 | 
| 16   | 145191.75 | 145174  | 144755.5 | 146666.75 | 
| 32   | 144624 | 145483.75 | 143531 | 145366.25 | 
| 64   | 145459.25 | 146175.75 | 147174.25 | 146622.5  | 
| 128   | 145625.75 | 143823.25 | 144132 | 144739.25 | 
| 256   | 145380.75 | 147224  | 146203.25 | 147078.75 | 
| 512   | 146045 | 145609.25 | 145149.25 | 144335.5  | 
| 1024  | 148280 | 148076  | 145593.25 | 146534.75 | 
+-------------+-----------+------------+-----------+--------------+ 
| Total  | 1454769 | 1457884.75 | 1454310 | 1456688.75 | 
+-------------+-----------+------------+-----------+--------------+ 

The test script I used can be found here

+1

+1 Этот ответ заслуживает большего количества голосов за усилия, связанные с получением реальных данных. – Jon

+1

Любая идея, в какую версию SQL Server были созданы эта статистика? –

+1

@MartinBrown - IIRC изначально 2008, хотя я недавно обновил тесты в 2012 году для самых последних изменений и нашел то же самое. –

Смежные вопросы