Один из администраторов баз данных в предыдущей жизни сказал мне, что когда я делаю предложение EXISTS
, используйте SELECT 1
вместо SELECT *
Ответ 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 Я использовал здесь