Книга: Настольная книга 1С:Эксперта по технологическим вопросам
Назад: 3.14.Планы запросов. Получение плана запроса в профайлере SQL. Операторы плана, наиболее важные для нас
Дальше: 3.15.Особенности чтения в объектной модели

Наиболее важные для нас операторы

Далее рассмотрим наиболее важные для нас операторы, разбитые по способу выполнения ими действий.

Сканирующие операторы

Сканирование – это получение всех записей. Разница состоит только в том, что именно проходится: кластерный индекс, некластерный индекс или сама таблица. Просмотрев все записи, сканирующие операторы возвращают ту их часть, которая подходит под условие, если оно задано необязательным предикатом WHERE:(). Детали по таким операторам приведены в таблице 3.14.2. Пример плана можно посмотреть в этой главе выше (см. пример 1).

Таблица 3.14.2. Сканирующие операторы плана запроса

Значок Название Описание
Clustered Index Scan Оператор Clustered Index Scan сканирует кластерный индекс, указанный в столбце Argument. Clustered Index Scan является логическим и физическим оператором
Index Scan Оператор Index Scan получает все записи некластерного индекса, указанного в столбце Argument. Index Scan является логическим и физическим оператором
Table Scan Оператор Table Scan получает строки из таблицы, указанной в столбце Argument плана выполнения запроса. Table Scan является логическим и физическим оператором

Опасность сканирования состоит в двух вещах: времени выполнения (особенно для Table Scan) и избыточных блокировках.

Время выполнения операторов сканирования линейно растет с ростом сканируемых объектов.

Сканирование не приводит к избыточным блокировкам в двух случаях:

Table Scan OBJECT:([tempdb].[dbo].[#tt3] AS [T2]) [T2].[_Q_000_F_000RRef], [T2].[_Q_000_F_001]

План запроса может считаться неоптимальным, если в нем встречается оператор сканирования, и при этом:

Особо следует указать оператор Constant Scan. В основном потому, что из-за его имени ему могут приписывать несвойственную ему функциональность. Подробности приведены в таблице 3.14.3.

Таблица 3.14.3. Оператор Constant Scan

Значок Название Описание
Constant Scan Оператор Constant Scan вводит в запрос одну или несколько константных строк. После выполнения оператора Constant Scan часто используется оператор Compute Scalar, который добавляет столбцы в строки, полученные в результате выполнения оператора Constant Scan. Оператор Constant Scan никак не связан с таблицей (таблицами) констант «1С»

Операторы поиска по индексу

Эти операторы первоначально считывают только строки, найденные по индексу с указанным в предикате SEEK:() условием поиска. Если этого не хватает для полного выполнения условий, может включаться необязательный предикат WHERE:(), в котором подсистема хранилища вычисляет выражение для всех строк, удовлетворяющих предикату SEEK:(). Предикат WHERE:() уже не использует индекс. Детали по таким операторам приведены в таблице 3.14.4. Примеры плана можно посмотреть в этой главе выше (см. пример 2) и ниже (см. примеры 3 и 4).

Таблица 3.14.4. Операторы плана запроса, использующие поиск по индексу

Значок Название Описание
Clustered Index Seek Оператор Clustered Index Seek использует поисковые возможности индексов для получения строк из кластерного индекса, указанного в столбце Argument. Clustered Index Seek – это логический и физический оператор
Index Seek Оператор Index Seek использует возможности поиска по индексам для получения строк из некластерного индекса, указанного в столбце Argument. Index Seek является логическим и физическим оператором

Оператор поиска по индексу может приводить к избыточным блокировкам, если в нем есть необязательный предикат WHERE:(), потому что в этом случае все равно блокируются все строки, прочитанные по условию предиката SEEK:().

План запроса может считаться неоптимальным, если в нем встречается оператор поиска по индексу, и при этом:

Пример 3

В той же самой базе «Бухгалтерии предприятия» выполним запрос:

ВЫБРАТЬ * ИЗ РегистрБухгалтерии.Хозрасчетный КАК Хозрасчетный

ГДЕ Хозрасчетный.Период = &Период

У основной таблицы регистра бухгалтерии, с которой мы работаем, есть подходящий индекс (Разделитель данных) + Период + Регистратор + НомерСтроки. Все условия могут быть проверены с помощью этого кластерного индекса. План запроса:

Rows          Executes     StmtText

----          --------     --------

17         1         Clustered Index Seek(OBJECT:([unt3].[dbo].[_AccRg7172].[_AccRg7172_ByPeriod_TRN] AS [T1]), SEEK:([T1].[_Fld10847]=[@P1] AND [T1].[_Period]=[@P2]) ORDERED FORWARD)

Пример 4

Допишем еще одно условие в запрос:

ВЫБРАТЬ   * ИЗ РегистрБухгалтерии.Хозрасчетный КАК Хозрасчетный

ГДЕ Хозрасчетный.Период = &Период

И Хозрасчетный.НомерСтроки = 3"

У основной таблицы регистра бухгалтерии, с которой мы работаем, как говорилось выше, есть кластерный индекс (Разделитель данных) + Период + Регистратор + НомерСтроки. Но поля не идут подряд, то есть между ними «вклинивается» поле Регистратор, не участвующее в условии запроса. Поэтому оптимизатор добавляет предикат WHERE:(). План запроса:

Rows         Executes     StmtText

----         --------     --------

1           1            Clustered Index Seek (OBJECT:([unt3].[dbo].[_AccRg7172].[_AccRg7172_ByPeriod_TRN] AS [T1]), SEEK:([T1].[_Fld10847]=[@P1] AND [T1].[_Period]=[@P2]),  WHERE:([unt3].[dbo].[_AccRg7172].[_LineNo] as [T1].[_LineNo]=[@P3]) ORDERED FORWARD)

Как было видно из примера 3, по условию из предиката SEEK:() считываются и блокируются 17 строк. Из результатов текущего запроса видно, что по совокупности условий предикатов SEEK:() и WHERE:() на самом деле нужна только одна строка, то есть запрос приводит к избыточным блокировкам. 17 строк это само по себе не много, но это число может расти. План запроса при этом нельзя считать неоптимальным: видно, что оптимизатор действительно сделал все, что мог, и виноват в избыточных блокировках не план, а автор запроса.

Операторы соединения

Эти операторы выполняют логические операции соединения результатов выполнения других операторов, также интерпретируемых как таблицы. Обычно рассматриваются в противопоставлении: Nested Loops (вложенные циклы, вложенные петли) либо любой другой оператор соединения, одним из которых является оператор Merge Join. Детали по этим операторам приведены в таблице 3.14.5.

Таблица 3.14.5. Операторы соединения

Значок Название Описание
Nested Loops Оператор Nested Loops выполняет логические операции внутреннего соединения, левого внешнего соединения, левого полусоединения и антилевого полусоединения. Операции соединения вложенных циклов выполняют поиск во внутренней таблице для каждой строки внешней таблицы. Возвращается результат выполнения логической операции над любыми строками, удовлетворяющими необязательному предикату в столбце Argument. Nested Loops является физическим оператором
Merge Join Оператор Merge Join выполняет внутреннее соединение, левое внешнее соединение, левое полусоединение, левое антиполусоединение, правое внешнее соединение, правое полусоединение, правое антиполусоединение, а также логические операции соединения.
В столбце Argument оператор Merge Join содержит предикат MERGE:(), если операция устанавливает соединение «один ко многим», или предикат MANY-TO-MANY MERGE:(), если операция устанавливает соединение «многие ко многим». Столбец Argument содержит также список столбцов, используемых для выполнения операции с разделителями-запятыми. Соединение слиянием особенно эффективно в случаях, когда явной сортировки не требуется, например, когда в базе данных имеется подходящий индекс, Merge Join является физическим оператором

Nested Loops – самый простой способ соединения таблиц. Затраты на его выполнение минимальны. Из-за этого оптимизатор SQL Sever использует этот оператор всегда, когда не может определить, как именно правильнее соединять таблицы. Это может происходить просто по ошибке, но обычно причина все-таки есть. Такой причиной может быть неактуальная статистика. Чаще всего, однако, это не неактуальная статистика, а ее отсутствие, что всегда имеет место, когда в запросе на языке «1С» используются соединения с вложенными запросами, соединения с виртуальными таблицами, подзапросы в условиях или в условиях соединения.

Пример 5

В той же самой базе «Бухгалтерии предприятия» выполним запрос:

ВЫБРАТЬ * ИЗ РегистрБухгалтерии.Хозрасчетный КАК Хозрасчетный

ГДЕ Хозрасчетный.Регистратор В

(ВЫБРАТЬ Документ.РеализацияТоваровУслуг.Ссылка

ИЗ Документ. РеализацияТоваровУслуг)

В условии используется подзапрос, поэтому логично ожидать появления оператора Nested Loops. Планы показаны на рисунках 3.14.4 и 3.14.5.

Рис. 3.14.4. Текстовый план запроса для примера 5 (фрагмент)

Рисунок 3.14.5. Графический план запроса для примера 5

Недостаток использования оператора Nested Loops заключается в том, что при соединении таблиц запрос к внутренней (в обоих планах ниже расположенной) таблице выполняется столько раз, сколько строк во внешней (выше расположенной). Если речь идет о соединении таблиц, по которым нет статистики, вся работа предоставлена на волю случая (это на самом деле и произошло в примере), что может прямо явиться причиной критической деградации производительности при выполнении таких запросов. В текстовой форме плана запроса в примере видно, что запрос в самой нижней строчке выполняется 51 раз, при этом он возвращает 51 строку, то есть 1 строку за обращение. Тот самый случай: 51 раз ходить к шкафу, доставая оттуда по листочку. Кроме того, с ростом базы верхняя таблица может становиться все больше, и это является объективным фактором роста вероятности деградации производительности.

Пример 6

В заключение продемонстрируем, как работает другой способ соединения. Для этого выполним запрос:

ВЫБРАТЬ Хозрасчетный.*,   ХозрасчетныйСубконто.* 

ИЗ РегистрБухгалтерии.Хозрасчетный КАК Хозрасчетный

ВНУТРЕННЕЕ СОЕДИНЕНИЕ

РегистрБухгалтерии.Хозрасчетный.Субконто КАК ХозрасчетныйСубконто

ПО Хозрасчетный.Период = ХозрасчетныйСубконто.Период 

И Хозрасчетный.Регистратор = ХозрасчетныйСубконто.Регистратор

И Хозрасчетный.НомерСтроки = ХозрасчетныйСубконто.НомерСтроки

Планы показаны на рис. 3.14.6 и 3.14.7.

Рис. 3.14.6. Текстовый план запроса для примера 6 (фрагмент)

Рис. 3.14.7. Графический план запроса для примера 6

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

Назад: 3.14.Планы запросов. Получение плана запроса в профайлере SQL. Операторы плана, наиболее важные для нас
Дальше: 3.15.Особенности чтения в объектной модели