При работе с информационными базами 1С нередко возникают ситуации, когда старые документы, заведенные много лет назад, содержат неполные данные. Это может быть связано с особенностями учета в прошлом, изменениями в законодательстве или просто ошибками ввода. В рассматриваемом случае мы столкнулись с проблемой, когда список счетов-фактур (СФ), как выданных, так и полученных, открывается с заметными задержками. Давайте разберем эту проблему и выясним, как ее эффективно решить.
Суть проблемы:
Основная причина подтормаживания заключается в том, что для документов до 2011 года не были заполнены ключевые реквизиты, такие как Контрагент, Сумма и ВалютаДок-та. Вместо прямого хранения этих данных в самом документе СФ, система была настроена таким образом, что при выводе списка — в процедуре ПриВыводеСтроки — эти реквизиты считываются из документа-основания. Поскольку процедура ПриВыводеСтроки вызывается для каждой отображаемой строки списка, многократные обращения к базе данных для получения данных из документов-оснований приводят к значительному замедлению отображения формы. Представим, что у нас тысячи старых счетов-фактур, и для каждой из них система вынуждена "идти" в базу, чтобы найти документ-основание и вытащить оттуда три реквизита. Это и есть главный источник тормозов.
Один из наиболее радикальных и эффективных способов решения проблемы — это единоразовое массовое заполнение недостающих реквизитов непосредственно в самих документах СФ. После такого заполнения необходимость считывать данные из документов-оснований в процедуре вывода списка отпадет, что значительно ускорит работу формы. Давайте рассмотрим этот подход подробнее.
Шаги по массовому заполнению реквизитов:
Контрагент, Сумма, ВалютаДок-та.ОбменДанными.Загрузка = Истина. Установив этот флаг перед записью документа, мы даем платформе понять, что изменения вносятся в режиме загрузки данных, и при этом не будут запускаться обработчики проведения документа. Это минимизирует риск изменения проводок и других учетных данных, которые могли бы «разъехаться».
// Пример кода в обработке для массового заполнения
ОбменДанными.Загрузка = Истина; // Устанавливаем флаг загрузки данных
ВыборкаДокументов = Документы.СчетФактураВыданный.Выбрать(НачалоГода(ДатаЗаполнения), КонецГода(ДатаЗаполнения));
Пока ВыборкаДокументов.Следующий() Цикл
ДокументСФ = ВыборкаДокументов.ПолучитьОбъект();
// Проверяем, нужно ли заполнять реквизиты
Если ДокументСФ.Контрагент.Пустая() ИЛИ ДокументСФ.Сумма = 0 ИЛИ ДокументСФ.ВалютаДокумента.Пустая() Тогда
// Пытаемся получить документ-основание
Если ДокументСФ.ДокументОснование.ЗначениеЗаполнено() Тогда
ДокументОснование = ДокументСФ.ДокументОснование.ПолучитьОбъект();
Если ДокументОснование <> Неопределено Тогда
// Заполняем реквизиты из документа-основания
Если ДокументСФ.Контрагент.Пустая() Тогда
ДокументСФ.Контрагент = ДокументОснование.Контрагент;
КонецЕсли;
Если ДокументСФ.Сумма = 0 Тогда
ДокументСФ.Сумма = ДокументОснование.СуммаДокумента; // Или другой реквизит суммы
КонецЕсли;
Если ДокументСФ.ВалютаДокумента.Пустая() Тогда
ДокументСФ.ВалютаДокумента = ДокументОснование.ВалютаДокумента;
КонецЕсли;
Попытка
ДокументСФ.Записать();
Исключение
Сообщить("Ошибка записи документа " + ДокументСФ + ": " + ОписаниеОшибки());
КонецПопытки;
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЦикла;
ОбменДанными.Загрузка = Ложь; // Снимаем флаг загрузки данных
Сообщить("Массовое заполнение завершено.");
ОбменДанными.Загрузка = Истина и не перепроводим документы, а лишь записываем их, то влияние на закрытые периоды будет минимальным. Откройте период только для выполнения обработки, затем закройте его снова. Важно понимать, что мы не изменяем движения по регистрам, а лишь заполняем реквизиты шапки документа.Риски и соображения при массовом заполнении:
Если массовое заполнение документов по каким-либо причинам неприемлемо (например, строгий запрет бухгалтерии на изменение старых данных), мы можем оптимизировать работу формы, изменив логику получения и отображения данных. Наша задача — убрать ресурсоемкие операции из процедуры ПриВыводеСтроки.
Принцип работы и оптимизация:
ПриВыводеСтроки: Процедура ПриВыводеСтроки вызывается для каждой отображаемой строки списка, поэтому любые запросы к базе данных или сложные вычисления внутри нее будут значительно замедлять работу формы. Если нам необходимо отображать вычисляемые или дополнительные данные, следует избегать обращения к ссылочным значениям напрямую в этой процедуре, поскольку это может приводить к многократному получению значений реквизитов из базы данных для каждой строки. В нашем случае, именно это и происходит: для каждой строки СФ система пытается "получить объект" документа-основания.ПриПолученииДанных для предварительной выборки информации: Это событие вызывается значительно реже, чем ПриВыводеСтроки (обычно один раз при открытии списка и при его обновлении). В обработчике события ПриПолученииДанных мы можем получить массив ссылок отображаемых строк списка и затем одним запросом (пакетным запросом) выбрать все необходимые дополнительные реквизиты или вычисляемые значения из документов-оснований для всех этих ссылок. После получения данных мы можем сохранить их, например, во временной таблице или коллекции, чтобы затем быстро к ним обращаться в ПриВыводеСтроки.
// Пример логики в модуле формы для обычных форм (фрагмент)
Перем КэшДанныхОснований; // Переменная для хранения кэшированных данных
Процедура ПриОткрытии()
// Инициализация кэша
КэшДанныхОснований = Новый ТаблицаЗначений();
КэшДанныхОснований.Колонки.Добавить("Ссылка");
КэшДанныхОснований.Колонки.Добавить("Контрагент");
КэшДанныхОснований.Колонки.Добавить("Сумма");
КэшДанныхОснований.Колонки.Добавить("Валюта");
КонецПроцедуры
Процедура СписокПриПолученииДанных(Элемент, Отказ)
СписокСсылок = Новый Массив();
Для Каждого СтрокаТЗ Из Элемент.ВыделенныеСтроки Цикл // Или по всем строкам, если это динамический список
СписокСсылок.Добавить(СтрокаТЗ.Ссылка);
КонецЦикла;
// Если список ссылок не пуст, делаем один запрос к базе
Если СписокСсылок.Количество() > 0 Тогда
Запрос = Новый Запрос();
Запрос.Текст =
"ВЫБРАТЬ
| СФ.Ссылка КАК СсылкаСФ,
| СФ.ДокументОснование.Контрагент КАК КонтрагентОснования,
| СФ.ДокументОснование.СуммаДокумента КАК СуммаОснования,
| СФ.ДокументОснование.ВалютаДокумента КАК ВалютаОснования
|ИЗ
| Документ.СчетФактураВыданный КАК СФ
|ГДЕ
| СФ.Ссылка В(&СписокСсылок)";
Запрос.УстановитьПараметр("СписокСсылок", СписокСсылок);
РезультатЗапроса = Запрос.Выполнить().Выгрузить();
// Очищаем и заполняем кэш
КэшДанныхОснований.Очистить();
Для Каждого СтрокаРезультата Из РезультатЗапроса Цикл
НоваяСтрокаКэша = КэшДанныхОснований.Добавить();
НоваяСтрокаКэша.Ссылка = СтрокаРезультата.СсылкаСФ;
НоваяСтрокаКэша.Контрагент = СтрокаРезультата.КонтрагентОснования;
НоваяСтрокаКэша.Сумма = СтрокаРезультата.СуммаОснования;
НоваяСтрокаКэша.Валюта = СтрокаРезультата.ВалютаОснования;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Процедура СписокПриВыводеСтроки(Элемент, ОформлениеСтроки, ДанныеСтроки)
// Если реквизиты СФ не заполнены, берем из кэша
Если ДанныеСтроки.Контрагент.Пустая() ИЛИ ДанныеСтроки.Сумма = 0 ИЛИ ДанныеСтроки.ВалютаДокумента.Пустая() Тогда
СтрокаКэша = КэшДанныхОснований.Найти(ДанныеСтроки.Ссылка, "Ссылка");
Если СтрокаКэша <> Неопределено Тогда
Если ДанныеСтроки.Контрагент.Пустая() Тогда
ОформлениеСтроки.Ячейки.Контрагент.Текст = СтрокаКэша.Контрагент; // Или другое имя поля в списке
КонецЕсли;
Если ДанныеСтроки.Сумма = 0 Тогда
ОформлениеСтроки.Ячейки.Сумма.Текст = СтрокаКэша.Сумма;
КонецЕсли;
Если ДанныеСтроки.ВалютаДокумента.Пустая() Тогда
ОформлениеСтроки.Ячейки.Валюта.Текст = СтрокаКэша.Валюта;
КонецЕсли;
Иначе
// Если в кэше нет, но реквизиты пустые, то просто выводим пустые значения
// чтобы не стучаться в базу
Если ДанныеСтроки.Контрагент.Пустая() Тогда
ОформлениеСтроки.Ячейки.Контрагент.Текст = "";
КонецЕсли;
Если ДанныеСтроки.Сумма = 0 Тогда
ОформлениеСтроки.Ячейки.Сумма.Текст = "0";
КонецЕсли;
Если ДанныеСтроки.ВалютаДокумента.Пустая() Тогда
ОформлениеСтроки.Ячейки.Валюта.Текст = "";
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Начиная с версии платформы 8.3.10 существует событие динамического списка ПриПолученииДанныхНаСервере, которое дает возможность выводить любую информацию в список и дополнительно оптимизировать процесс, поскольку данные извлекаются только для видимых строк. Если вы работаете в управляемых формах и более новой версии платформы, это будет предпочтительный вариант.
ИЛИ, НЕ, ПОДСТРОКА, МЕСЯЦ, ВЫБОР КОГДА и динамических (вычисляемых) полей. Эти элементы могут значительно замедлять выполнение запроса на сервере. Мы должны стремиться к максимально простым и индексированным запросам.ПриВыводеСтроки для ячеек, отображающих ссылочные значения, устанавливать в качестве текста уже полученное строковое представление. Это позволит избежать получения строковых представлений при каждом обновлении табличного поля.Помимо рассмотренных специфических решений, существуют общие подходы к оптимизации производительности 1С, которые могут помочь в нашем случае и при работе с системой в целом. Давайте рассмотрим некоторые из них.
Применение одного или комбинации этих решений поможет нам значительно ускорить работу формы списка документов и повысить общую производительность системы 1С.