При работе с большими базами данных в 1С:Предприятие 7.7 (в нашем случае база объемом 50 Гб на SQL Server) стандартные механизмы запросов часто начинают работать неудовлетворительно медленно. Одной из типичных задач является поиск всех документов определенного вида (например, УстановкаЦен), в табличной части которых присутствует конкретная номенклатурная позиция. Рассмотрим подробно, как можно оптимизировать этот процесс, начиная от простых правок кода и заканчивая переходом на прямые SQL-запросы.
Первое, что мы можем сделать, не меняя архитектуру системы — это проанализировать текущий код запроса. Разберем типичную ошибку вызова функций внутри текста условия. Проанализируем пример кода:
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса = "
|ОбрабатыватьДокументы Все;
|Обрабатывать НеПомеченныеНаУдаление;
|ТекДок = Документ.УстановкаЦен.ТекущийДокумент;
|Товар = Документ.УстановкаЦен.Номенклатура;
|Группировка ТекДок;
|Условие (Товар = ТекущийЭлемент());
|";
В данном случае функция ТекущийЭлемент() будет вызываться 1С для каждой строки обрабатываемых документов. Если документов в базе сотни тысяч, это создает огромные накладные расходы. Чтобы ускорить выполнение, нам необходимо вычислить значение один раз и передать его в запрос через переменную. Посмотрим на исправленный вариант:
МойТовар = ТекущийЭлемент(); // Вычисляем один раз
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса = "
|ОбрабатыватьДокументы Все;
|Обрабатывать НеПомеченныеНаУдаление;
|ТекДок = Документ.УстановкаЦен.ТекущийДокумент;
|Товар = Документ.УстановкаЦен.Номенклатура;
|Группировка ТекДок;
|Условие (Товар = МойТовар);
|";
Важное примечание: при использовании переменной в условии, SQL-версия 1С 7.7 может более эффективно построить план запроса, так как значение становится константой для движка базы данных.
Выясним причину, по которой поиск по табличной части документа в 1С 7.7 работает медленно. В SQL-базе данные табличных частей хранятся в таблицах DT*. Эти таблицы имеют индексы только по IDDOC (ссылка на документ) и LINENO (номер строки). Индекса по полю Номенклатура (в SQL это реквизит с именем типа SP*) в этой таблице нет. Следовательно, запрос выполняет полное сканирование (Table Scan) всех строк документов, что на базе в 50 Гб крайне долго.
Чтобы ускорить штатный поиск, мы можем использовать Графы отбора. Разберем по шагам, как это работает:
УстановкаЦен и отметим галочкой реквизит табличной части Номенклатура.После сохранения конфигурации 1С создаст в SQL-базе специальную индексированную таблицу _1SCRDOC. Теперь поиск через метод ВыбратьПоРеквизиту или использование этой графы в запросе будет происходить мгновенно, так как система будет обращаться к индексу, а не перебирать все документы.
Если штатных средств недостаточно или вы хотите добиться максимальной производительности, следует использовать внешнюю компоненту 1С++. Прямые запросы позволяют избежать создания временных DBF-файлов (которые 1С 7.7 создает даже в SQL-версии для обработки результатов запроса) и обращаются к данным напрямую.
Рассмотрим пример простого прямого запроса для получения списка документов по товару:
ТекстЗапроса = "
|SELECT
| Док.IDDOC [Док $Документ.УстановкаЦен]
|FROM
| $ДокументСтроки.УстановкаЦен AS Док
|WHERE
| $Док.Номенклатура = :Товар
|";
рс = СоздатьОбъект("ODBCRecordset");
рс.УстановитьТекстовыйПараметр("Товар", ТекущийЭлемент());
ТЗ = рс.ВыполнитьИнструкцию(ТекстЗапроса);
Однако этот запрос все еще будет выполнять сканирование таблицы строк. Чтобы сделать его по-настоящему быстрым, объединим его с журналом документов и добавим фильтры. Проанализируем более продвинутый пример:
ТекстЗапроса = "
|SELECT
| Жур.IDDOC [Док $Документ.УстановкаЦен],
| Жур.IDDOCDEF [Док_вид]
|FROM
| _1SJOURN AS Жур (NOLOCK)
|INNER JOIN
| $ДокументСтроки.УстановкаЦен AS Док (NOLOCK) ON Док.IDDOC = Жур.IDDOC
|WHERE
| $Док.Номенклатура = :Товар
| AND Жур.ISMARK = 0 -- Не помеченные на удаление
| AND Жур.CLOSED > 0 -- Только проведенные (если нужно)
|";
Преимущества данного подхода:
(NOLOCK) позволяет читать данные, не дожидаясь завершения транзакций других пользователей.Запрос.Таблицу Значений.Посмотрим на ситуацию под другим углом. Если документ УстановкаЦен при проведении меняет периодический реквизит «Цена» в справочнике Номенклатура, то информация об этом уже записана в таблицу констант (периодических реквизитов) _1SCONST.
Выясним, как получить документ через объект Периодический. Это может быть быстрее любого запроса к документам, так как таблица _1SCONST жестко индексирована по объектам (номенклатуре).
Пер = СоздатьОбъект("Периодический");
Пер.ИспользоватьОбъект("Цена", ТекущийЭлемент());
Пер.ВыбратьЗначения(ДатаНачала, ДатаКонца);
Пока Пер.ПолучитьЗначение() = 1 Цикл
Док = Пер.ТекущийДокумент();
// Обработка документа
КонецЦикла;
Или, если мы используем 1С++, мы можем обратиться к этой таблице напрямую через SQL. Это особенно полезно, если нам нужно найти только последний документ, установивший цену:
|SELECT TOP 1
| const.DOCID [Док $Документ]
|FROM
| _1SCONST const (NOLOCK)
|WHERE
| const.ID = $ИсторияРеквизита.Номенклатура.Цена
| AND const.OBJID = :ВыбТовар
|ORDER BY
| const.DATE DESC, const.TIME DESC, const.DOCID DESC
Для ускорения поиска документов в 1С 7.7 на больших SQL-базах мы рекомендуем придерживаться следующей стратегии:
Периодический или таблицу _1SCONST, так как это самый короткий путь к данным в обход тяжелых таблиц документов.Помните, что на базах объемом 50 Гб и выше, профилактика индексов и обновление статистики в SQL Server являются обязательными условиями быстродействия любого из перечисленных методов.