По мотивам //infostart.ru/public/165456/ ...
Итак, почему нижеописанный запрос стал тормозить?
ВЫБРАТЬ
Док.Номенклатура.Комплект КАК Комплект, --< соединение с "Номенклатура"
Док.Склад КАК Склад,
Док.ПроцентБонуса КАК ПроцентБонуса,
Док.Ссылка.Рецепт КАК Рецепт, --< соединение с "ЧекиККМ"
Док.СуммаАвтоматическойСкидки КАК СуммаАвтоматическойСкидки,
Док.ИнтернетЗаказ КАК ИнтернетЗаказ
ИЗ
Документ.ЧекККМ.Товары КАК Док
ГДЕ
Док.Ссылка.Дата МЕЖДУ &НачДата И &КонДата
И Док.Ссылка.КассаККМ = &КассаККМ
И Док.Ссылка.Проведен
В данном запросе есть соединение ТЧ с двумя таблицами: Документы.ЧекиККМ и Справочники.Номенклатура
Кто-то скажет, что этот запрос писал не очень грамотный программист. Но почему иногда он всё же будет работать быстро?
Вот текст SDBL и плана запроса до реструктуризации:
Fields:(
_Reference50_rd0z0d35._Fld779,
_Document152_VT3354_Q_000_T_001._Fld3370RRef,
_Document152_VT3354_Q_000_T_001._Fld5855,
_Document152_rd0z0d35._Fld5850RRef,
_Document152_VT3354_Q_000_T_001._Fld5857,
_Document152_VT3354_Q_000_T_001._Fld5858
)
_DOCUMENT152 (_Document152_rd0z0d35) RANGE SCAN USING INDEX (_DOCUMEN152_BYFIELD3376_RTR) (2 fields)
WHERE
(_Document152_rd0z0d35._Date_Time >= 20180326000000)
AND
(_Document152_rd0z0d35._Date_Time <= 20180326235959)
AND
(_Document152_rd0z0d35._Posted = TRUE)
NESTED LOOP
_DOCUMENT152_VT3354 (_Document152_VT3354_Q_000_T_001) RANGE SCAN USING INDEX (_DOCUMEN152_VT3354_INTKEYIND) (1 fields)
WHERE
(_Document152_VT3354_Q_000_T_001._Document152_IDRRef = _Document152_rd0z0d35._IDRRef)
NESTED OUTER LOOP
_REFERENCE50 (_Reference50_rd0z0d35) RANGE SCAN USING INDEX (_IDRREFIDX) (1 fields)
WHERE
(_Document152_VT3354_Q_000_T_001._Fld3356RRef = _Reference50_rd0z0d35._IDRRef)
Statistics: RecordsScanned = 0, ParseTime = 0, ExecuteTime = 0, BuffersMemory = 8192, ResultRecords = 0, RecordSize = 51
Выполняется за 16мс.
Тот же запрос, но после смены режима совместимости, добавления пары полей и рестуктуризации:
Fields:(
T2._Fld779,
T1._Fld3370RRef,
T1._Fld5855,
T3._Fld5850RRef,
T1._Fld5857,
T1._Fld5858
)
_DOCUMENT152_VT3354 (T1) FULL SCAN
NESTED OUTER LOOP
_REFERENCE50 (T2) RANGE SCAN USING INDEX (_IDRREFIDX) (1 fields)
WHERE
(T1._Fld3356RRef = T2._IDRRef)
NESTED LOOP
_DOCUMENT152 (T3) RANGE SCAN USING INDEX (_IDRREFIDX) (1 fields)
WHERE
(T3._Date_Time >= 20180326000000)
AND
(T3._Date_Time <= 20180326235959)
AND
(T3._Fld3341RRef = ABC7C89CDC2D6B5E11E14357A2462119)
AND
(T3._Posted = TRUE)
AND
(T1._Document152_IDRRef = T3._IDRRef)
Statistics: RecordsScanned = 530187, ParseTime = 1, ExecuteTime = 2979, BuffersMemory = 8192, ResultRecords = 0, RecordSize = 51
Выполняется дольше 5 секунд, количество считанных записей - полмиллиона! Запрос тормозит, т.к. sdbl (кстати, вообще кто-нибудь знает как он расшифровывается??) выбирает неоптимальный план.
Что заставило планировщик изменить порядок выборки с Товары<-ЧекККМ<-Номенклатура на Товары<-Номенклатура<-ЧекККМ ?
Наблюдение: После обновления конфигурации таблица товаров чека уехала с середины файловой базы в конец.
https://yadi.sk/i/LeXVBJhJ3ToM6q
В данном случае одинаковый запрос выполняется по-разному.
В каком порядке правильно писать соединения?
Рассмотрим ещё одну задачу:
Задача 2.
Необходимо получить весь список номенклатуры из всех документов «Отчет о розничных продажах» за указанный период. Существующий запрос выполняется слишком медленно:
ВЫБРАТЬ
ОтчетОРозничныхПродажахТовары.Номенклатура
ИЗ
Документ.ОтчетОРозничныхПродажах.Товары КАК ОтчетОРозничныхПродажахТовары
ГДЕ
ОтчетОРозничныхПродажахТовары.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
ОтчетОРозничныхПродажахТовары.Номенклатура
Предложить вариант оптимизации запроса.
Бытует мнение, что здесь сначала выберутся все документы, затем все товары из них, только потом наложится условие по дате на всё полное соединение и движку придется перебрать все товары всех документов. НО это не так!
Не знаю откуда это мнение кажется очевидным, может быть проводится интуитивная параллель с виртуальными таблицами... И предлагается вот такое решение:
2. Запрос:
ВЫБРАТЬ РАЗЛИЧНЫЕ
ОтчетОРозничныхПродажахТовары.Номенклатура
ИЗ
Документ.ОтчетОРозничныхПродажах.Товары КАК ОтчетОРозничныхПродажахТовары
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ОтчетОРозничныхПродажах КАК ШапкаДокумента
ПО ОтчетОРозничныхПродажахТовары.Ссылка = ШапкаДокумента.Ссылка
И ШапкаДокумента.Дата МЕЖДУ &НачалоПериода И &КонецПериода
Когда я встретился с этой задачей, я не знал как смотреть план запроса в файловой базе (ИР->КонсольЗапросов->ЗапросРезультата->Трасса) и не мог понять какое соединение правильней применять. Даже сейчас, замерив скорость выполнения я получаю одинаковые цифры.
Вот план исходной задачи:
Fields:(
T1._Fld2047RRef
)
_DOCUMENT117 (T2) RANGE SCAN USING INDEX (_DOCUMEN117_BYDOCDATE_TR) (1 fields)
NESTED LOOP
_DOCUMENT117_VT2039 (T1) RANGE SCAN USING INDEX (_DOCUMEN117_VT2039_INTKEYIND) (1 fields)
WHERE
(T1._Document117_IDRRef = T2._IDRRef)
GROUPING
Statistics: RecordsScanned = 20825, ParseTime = 0, ExecuteTime = 28, BuffersMemory = 228412, ResultRecords = 9747, RecordSize = 18
Вот план предлагаемого решения:
Fields:(
T1._Fld2047RRef
)
_DOCUMENT117 (T2) RANGE SCAN USING INDEX (_DOCUMEN117_BYDOCDATE_TR) (1 fields)
NESTED LOOP
_DOCUMENT117_VT2039 (T1) RANGE SCAN USING INDEX (_DOCUMEN117_VT2039_INTKEYIND) (1 fields)
WHERE
(T1._Document117_IDRRef = T2._IDRRef)
WITHOUT DUPLICATES
Statistics: RecordsScanned = 20825, ParseTime = 0, ExecuteTime = 39, BuffersMemory = 228412, ResultRecords = 9747, RecordSize = 18
Планы одинаковы! В данном случае способ соединения неважен. Если мы делаем левое соединение и накладываем условие на правую таблицу, то оно отрабатывает как внутреннее соединение согласно данному условию.
Здесь же разные запросы выполняются одинаково.
Вывод: не важно как написан запрос - важно как он выполнится. Но это особая, черная магия...
ps: казалось бы, причем тут разыменование?