Freigeben über


Одна запись из пустой таблицы.

 

Как известно, процесс выполнения запроса состоит из парсинга (проверка синтаксиса), нормализации (привязка метаданных, раскрытие представлений, уплощение подзапросов и пр.), компиляции (процедурных конструкций типа 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

 image001

Рис.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

image002 

Рис.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

 image003

Рис.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')

image004 

Рис.4

 

Можно еще превратить таблицу из кучи в дерево, что, впрочем, тоже не поколеблет решимости оптимизатора получить запись из пустоты:

 

create clustered index idx_t on dbo.t(id)

select id from dbo.t

image005

Рис.5

 

Вы скажете, в конце концов, велика ли разница? Ошибка в одну запись мало, на что способна повлиять. Допустим, если оптимизатор ожидает получить n записей, он будет применять индексный поиск, а если n+1, то скан. Когда ситуация балансирует на грани n - n+1, индексный поиск оказывается столь же неоптимален, как и полный скан таблицы.

 

Разница в одну запись очень велика, когда она оказывается разницей между нулем или не нулем. В частности, если результат запроса планируется пустым, то и выражения в списке SELECT оцениваться не будут, т.к. зачем, если все равно выводить нечего? Например, вот этот запрос отработает нормально:

 

select 1/0 from t where 1 = 0

 

image006

Рис.6

 

А этот вернет ошибку:

 

select 1/0 from t

 

image007

Рис.7

 

То есть даже если мы твердо знаем, что наш запрос не должен возвратить ни одной записи, выражения в списке вывода следует проверять на деление на 0 и прочие каверзы, потому что они могут подвергаться оценке в процессе его выполнения. Это противоречит опыту программирования на алгоритмических языках, которые позволяют выразить шаг за шагом необходимые для выполнения инструкции:

 

image008

Рис.8

 

Хотя справедливости ради стоит отметить, что компилятор до определенных пределов может вносить свои коррективы. Например, композитные условия в операторе if в некоторых языках могут оцениваться не в том порядке, как они были прописаны, поэтому там не стоит полагаться на то, что если Cond1 в конструкции if (Cond1 and Cond2) ложно, то до Cond2 дело не дойдет. Тем более это касается непроцедурного языка SQL, в котором запрос позволяет описать лишь то, что нужно делать, а как конкретно это будет делаться, определяется исполнительным механизмом СУБД.

 

И в завершение. Я не уверен, что дело здесь только в Estimated Row Number, потому что если заменить константу в SELECT на поле таблицы, план запроса не изменится по сравнению с Рис.7, но он не вызовет ошибки

 

select id/0 from t

select 1/0 from t

image009

Рис.9