Подзапрос с использованием Exists 1 или Exists *

Раньше я писал свои проверки 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

Нет. Это было покрыто миллиардом раз. 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, pg 191 http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt

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

Ответ 2

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

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, что означает, что он может остановить сканирование, как только будет возвращена первая строка.

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

Однако Конор Каннингем из команды Оптимизатор запросов объясняет здесь, который обычно использует 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)

Я обнаружил, что в тех случаях, когда таблица имеет 1024 байта в большинстве случаев, стек вызовов выглядит примерно так, как показано ниже, что он действительно тратит большую часть времени на метаданные столбца загрузки, даже когда используется 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 Функции 1024 столбца vs Top 15 Functions 1 column).

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

Пример, который я вырезал из беседы на куче

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   |
+-------------+-----------+------------+-----------+--------------+

Тест script Я использовал здесь

Ответ 3

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

Ответ 4

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

Ответ 5

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

Ответ 6

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