Причина этого заблуждения, по-видимому, связана с убеждением, что в итоге оно будет читать все столбцы. Легко видеть, что это не так.
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'
дает Планируйте
Это показывает, что 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)
, и измерил среднее число выполнений в секунду. Результаты ниже
+-------------+----------+---------+---------+--------------+
| 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)
, чтобы последующие исполнения могли использовать кешированный план, дали следующее.
+-------------+-----------+------------+-----------+--------------+
| 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
Вы забыли EXISTS (SELECT NULL FROM ...). Это было задано недавно btw –
p.s. получить новый администратор базы данных. Суеверие не имеет места в ИТ, особенно в управлении базами данных (от прежнего DBA !!!) –