В процессе разработки отчетов и пакетных обработок в 1С:Предприятии 7.7 программисты нередко сталкиваются с ситуацией, когда тяжелая таблица значений (особенно со сложной иерархией или вложенными подтаблицами) формируется в течение нескольких минут. Если пользователи обращаются к этим данным десятки раз за день, пересчитывать структуру заново при каждом открытии формы недопустимо. Глобальная переменная модуля конфигурации решает проблему только в рамках одного сеанса конкретного пользователя, но при закрытии программы или входе нового пользователя память очищается.
Разберем, как организовать надежное хранение подготовленной таблицы значений между сеансами, какие штатные механизмы платформы 7.7 позволяют моментально читать и записывать данные, а также проанализируем ошибки в алгоритмах, вызывающие критическое замедление работы системы.
Наиболее простое и быстрое решение для сохранения сформированной структуры — штатная сериализация объекта платформы во внешний файл. В типовом синтаксисе 1С 7.7 для этого предусмотрены функции ЗначениеВФайл() и ЗначениеИзФайла(). Они позволяют выгрузить всю таблицу значений со всеми вложенными объектами и типами в текстовый файл специального формата и впоследствии восстановить ее буквально за пару секунд.
Рассмотрим типовой сценарий реализации разделения подготовки и чтения данных:
Посмотрим на пример кода сохранения данных на стороне сервера или ночного скрипта:
// Формируем результирующую структуру данных
ТаблицаСертификатов = СоздатьОбъект("ТаблицаЗначений");
ТаблицаСертификатов.НоваяКолонка("Номер", "Число", 4, 0);
ТаблицаСертификатов.НоваяКолонка("Контрагент", "Справочник.Контрагенты");
ТаблицаСертификатов.НоваяКолонка("ТаблНоменклатура", "ТаблицаЗначений");
// ... (алгоритм наполнения таблицы значениями)
// Сохраняем объект на сетевой диск или локальный каталог базы
КаталогВыгрузки = КаталогИБ() + "ExtFiles\";
Если ФС.СуществуетКаталог(КаталогВыгрузки) = 0 Тогда
ФС.СоздатьКаталог(КаталогВыгрузки);
КонецЕсли;
ПутьКФайлу = КаталогВыгрузки + "CachedCertificates.dat";
ЗначениеВФайл(ПутьКФайлу, ТаблицаСертификатов);
Теперь разберем, как пользовательская обработка считывает сохраненную таблицу при открытии:
Процедура ПриОткрытии()
ПутьКФайлу = КаталогИБ() + "ExtFiles\CachedCertificates.dat";
ТаблицаСертификатов = СоздатьОбъект("ТаблицаЗначений");
Если ФС.СуществуетФайл(ПутьКФайлу) = 1 Тогда
ЗначениеИзФайла(ПутьКФайлу, ТаблицаСертификатов);
Иначе
Предупреждение("Кэшированный файл не найден! Выполните расчет вручную.");
КонецЕсли;
КонецПроцедуры
Аналогичным образом можно использовать пару функций ЗначениеВСтрокуВнутр() и ЗначениеИзСтрокиВнутр(), если данные требуется упаковать в строковую переменную для передачи или помещения в строковый реквизит.
Важные риски и ограничения сериализации:
ЗначениеВФайл перезаписывает файл целиком без разделения доступа. Если пользователь выполняет чтение в тот момент, когда сервер обновляет файл, возникнет ошибка совместного доступа файловой системы. Для защиты рекомендуется производить запись во временный файл с последующим быстрым переименованием.Если требуется хранить информацию непосредственно в базе данных под управлением транзакционной целостности и избежать проблем с правами доступа к общей папке, лучшим решением является создание служебного объекта метаданных:
ЗначениеВСтрокуВнутр().Этот способ устраняет конфликт файловых блокировок антивирусами или сетевыми дисками и обеспечивает автоматическое резервное копирование кэша вместе со всей информационной базой.
Проанализируем исходный код формирования таблицы, в котором происходили основные задержки, и выясним причину медленной работы. В большинстве случаев столь длительное время вызвано не особенностями платформы 1С 7.7, а неоптимальными алгоритмическими конструкциями.
УдалитьСтроку() внутри перебора таблицы значений приводит к физическому сдвигу всех нижележащих строк в памяти и непрерывной фрагментации пула ОЗУ.Посмотрим, как оптимизировать алгоритм группировки и устранить квадратичную сложность с помощью предварительной сортировки. Заменим вложенные циклы одним последовательным проходом:
// Сортируем исходную плоскую таблицу по контрагенту
ТаблИзФайлаНовая.Сортировать("Контрагент+");
ТаблицаСертификатов = СоздатьОбъект("ТаблицаЗначений");
ТаблицаСертификатов.НоваяКолонка("Номер", "Число", 4, 0);
ТаблицаСертификатов.НоваяКолонка("Документ", "Строка", 50);
ТаблицаСертификатов.НоваяКолонка("Контрагент", "Справочник.Контрагенты");
ТаблицаСертификатов.НоваяКолонка("ТаблНоменклатура", "ТаблицаЗначений");
ТекКонтрагент = 0;
ТЗНоменклатура = "";
Номер = 0;
ТаблИзФайлаНовая.ВыбратьСтроки();
Пока ТаблИзФайлаНовая.ПолучитьСтроку() = 1 Цикл
// Проверяем смену контрагента (линейный проход)
Если ТаблИзФайлаНовая.Контрагент <> ТекКонтрагент Тогда
ТекКонтрагент = ТаблИзФайлаНовая.Контрагент;
Номер = Номер + 1;
ТаблицаСертификатов.НоваяСтрока();
ТаблицаСертификатов.Номер = Номер;
ТаблицаСертификатов.Документ = "Реализация (Купля-Продажа)";
ТаблицаСертификатов.Контрагент = ТекКонтрагент;
// Создаем новую подтаблицу номенклатуры для текущего контрагента
ТЗНоменклатура = СоздатьОбъект("ТаблицаЗначений");
ТЗНоменклатура.НоваяКолонка("Номенклатура", "Справочник.Номенклатура");
ТаблицаСертификатов.ТаблНоменклатура = ТЗНоменклатура;
КонецЕсли;
// Добавляем позицию в текущую вложенную таблицу
ТЗНоменклатура.НоваяСтрока();
ТЗНоменклатура.Номенклатура = ТаблИзФайлаНовая.Номенклатура;
КонецЦикла;
Благодаря замене вложенного перебора на предварительную сортировку и однократный проход время построения структуры из 5 000 строк сокращается с 5-7 минут до долей секунды, что в большинстве сценариев снимает необходимость даже промежуточного сохранения на диск.