Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Как известно, процесс выполнения запроса состоит из парсинга (проверка синтаксиса), нормализации (привязка метаданных, раскрытие представлений, уплощение подзапросов и пр.), компиляции (процедурных конструкций типа if, циклов) и оптимизации (собственно, SQL-запросов, которые, по определению, являются непроцедурными, т.е. в них описывается лишь то, что хочется получить, но не как). При построении процедурного плана выполнения SQL-запроса, в расчет, в частности, принимаются статистики распределения величин в колонках. Как известно, оптимизатор априoри оценивает мощность результирующего множества в одну запись в случае табличных переменных:
set showplan_all on
go
declare @t table (id int identity)
insert @t default values; insert @t default values; insert @t default values;
select * from @t
go
set showplan_all off
Рис.1
табличных функций (не inline) и др., когда статистики нет, потому что ее изначально не ведется, и сказать что-либо определенное про то, сколько там на выходе будет записей, нельзя.
Но, черт возьми, Холмс (с). Почему из нормальной пустой таблицы он тоже собирается возвращать одну запись?
if OBJECT_ID('dbo.t', 'U') is not null drop table t
create table t (id int)
set statistics profile on
set statistics io on
select id from t
set statistics io off
set statistics profile off
Рис.2
Хотя если посмотреть на вывод statistics io на закладке Messages , там значится
Table 't'. Scan count 1, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Оптимизатор «додумается», что записей нет, если, скажем, в условии where поставить заведомо ложное условие. Меняем запрос и смотрим Estimated Plan.
select id from t where 1 = 0
Рис.3
Имеем 0 записей. Видите – может, когда захочет.
Отсутствие статистики над таблицей t
exec sp_helpstats 'dbo.t'
------------------------
This object does not have any statistics.
не оказывает влияния, т.к. план запроса в данном случае тривиален и не требует статистики распределения величин в колонках. Можно ее создать принудительно, все равно она будет пустой:
create statistics t_stats on dbo.t(id) with fullscan
dbcc show_statistics ('dbo.t', 't_stats')
Рис.4
Можно еще превратить таблицу из кучи в дерево, что, впрочем, тоже не поколеблет решимости оптимизатора получить запись из пустоты:
create clustered index idx_t on dbo.t(id)
select id from dbo.t
Рис.5
Вы скажете, в конце концов, велика ли разница? Ошибка в одну запись мало, на что способна повлиять. Допустим, если оптимизатор ожидает получить n записей, он будет применять индексный поиск, а если n+1, то скан. Когда ситуация балансирует на грани n - n+1, индексный поиск оказывается столь же неоптимален, как и полный скан таблицы.
Разница в одну запись очень велика, когда она оказывается разницей между нулем или не нулем. В частности, если результат запроса планируется пустым, то и выражения в списке SELECT оцениваться не будут, т.к. зачем, если все равно выводить нечего? Например, вот этот запрос отработает нормально:
select 1/0 from t where 1 = 0
Рис.6
А этот вернет ошибку:
select 1/0 from t
Рис.7
То есть даже если мы твердо знаем, что наш запрос не должен возвратить ни одной записи, выражения в списке вывода следует проверять на деление на 0 и прочие каверзы, потому что они могут подвергаться оценке в процессе его выполнения. Это противоречит опыту программирования на алгоритмических языках, которые позволяют выразить шаг за шагом необходимые для выполнения инструкции:
Рис.8
Хотя справедливости ради стоит отметить, что компилятор до определенных пределов может вносить свои коррективы. Например, композитные условия в операторе if в некоторых языках могут оцениваться не в том порядке, как они были прописаны, поэтому там не стоит полагаться на то, что если Cond1 в конструкции if (Cond1 and Cond2) ложно, то до Cond2 дело не дойдет. Тем более это касается непроцедурного языка SQL, в котором запрос позволяет описать лишь то, что нужно делать, а как конкретно это будет делаться, определяется исполнительным механизмом СУБД.
И в завершение. Я не уверен, что дело здесь только в Estimated Row Number, потому что если заменить константу в SELECT на поле таблицы, план запроса не изменится по сравнению с Рис.7, но он не вызовет ошибки
select id/0 from t
select 1/0 from t
Рис.9