Как ускорить работу списка документов в 1С 8.1, когда реквизиты не заполнены?

Программист 1С v8.3 (Обычные формы) 1С:ERP Управление предприятием Бухгалтерский учет
← На главную

При работе с информационными базами 1С нередко возникают ситуации, когда старые документы, заведенные много лет назад, содержат неполные данные. Это может быть связано с особенностями учета в прошлом, изменениями в законодательстве или просто ошибками ввода. В рассматриваемом случае мы столкнулись с проблемой, когда список счетов-фактур (СФ), как выданных, так и полученных, открывается с заметными задержками. Давайте разберем эту проблему и выясним, как ее эффективно решить.

Суть проблемы:

Основная причина подтормаживания заключается в том, что для документов до 2011 года не были заполнены ключевые реквизиты, такие как Контрагент, Сумма и ВалютаДок-та. Вместо прямого хранения этих данных в самом документе СФ, система была настроена таким образом, что при выводе списка — в процедуре ПриВыводеСтроки — эти реквизиты считываются из документа-основания. Поскольку процедура ПриВыводеСтроки вызывается для каждой отображаемой строки списка, многократные обращения к базе данных для получения данных из документов-оснований приводят к значительному замедлению отображения формы. Представим, что у нас тысячи старых счетов-фактур, и для каждой из них система вынуждена "идти" в базу, чтобы найти документ-основание и вытащить оттуда три реквизита. Это и есть главный источник тормозов.

Решение 1: Массовое заполнение реквизитов исторических документов

Один из наиболее радикальных и эффективных способов решения проблемы — это единоразовое массовое заполнение недостающих реквизитов непосредственно в самих документах СФ. После такого заполнения необходимость считывать данные из документов-оснований в процедуре вывода списка отпадет, что значительно ускорит работу формы. Давайте рассмотрим этот подход подробнее.

Шаги по массовому заполнению реквизитов:

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

Риски и соображения при массовом заполнении:

  1. Согласование с бухгалтерией: Хотя мы используем безопасный способ записи, бухгалтеры могут быть категорически против любых изменений в исторических данных. Нам предстоит объяснить, что это изменение метаданных, а не учетных данных, и оно не повлияет на финансовую отчетность. Однако, если документ-основание был изменен после создания СФ, или если были ошибки в первоначальном заполнении, массовое заполнение может "закрепить" эти ошибки или привести к расхождениям, которые ранее могли быть не замечены. Поэтому важно тщательно проанализировать логику заполнения.
  2. Механизм "История данных": Начиная с платформы 8.3.11, в 1С реализован механизм "История данных". Он компактно хранит историю изменений прикладных данных. Если вы используете современную платформу и этот механизм активирован, он позволит отслеживать, кто, когда и что изменил в объекте, а также сравнивать версии и восстанавливать данные. Это может быть полезно для контроля изменений и анализа последствий, если все же придется вносить изменения в старые документы.
  3. Изменение периодических реквизитов: Если реквизиты, которые мы хотим изменить, являются периодическими и их значения были установлены другими документами, прямое изменение может быть заблокировано платформой с ошибкой "Значение установлено документом!". В таких случаях может потребоваться использование более сложных методов, вплоть до прямых запросов к базе данных (например, средствами SQL), но это крайне рискованный метод, требующий глубоких знаний структуры базы и использования только высококвалифицированными специалистами. Для рассматриваемого случая (реквизиты шапки документа) это, скорее всего, неактуально.

Решение 2: Оптимизация отображения данных без изменения документов

Если массовое заполнение документов по каким-либо причинам неприемлемо (например, строгий запрет бухгалтерии на изменение старых данных), мы можем оптимизировать работу формы, изменив логику получения и отображения данных. Наша задача — убрать ресурсоемкие операции из процедуры ПриВыводеСтроки.

Принцип работы и оптимизация:

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

    Начиная с версии платформы 8.3.10 существует событие динамического списка ПриПолученииДанныхНаСервере, которое дает возможность выводить любую информацию в список и дополнительно оптимизировать процесс, поскольку данные извлекаются только для видимых строк. Если вы работаете в управляемых формах и более новой версии платформы, это будет предпочтительный вариант.

  3. Оптимизация запроса динамического списка: Для управляемых форм, если используется динамический список, первым шагом к его ускорению является оптимизация запроса. Важно использовать индексы при соединении таблиц, избегать вложенных запросов, большого количества таблиц, условий ИЛИ, НЕ, ПОДСТРОКА, МЕСЯЦ, ВЫБОР КОГДА и динамических (вычисляемых) полей. Эти элементы могут значительно замедлять выполнение запроса на сервере. Мы должны стремиться к максимально простым и индексированным запросам.
  4. Флаг "Динамическое считывание данных": В свойствах запроса динамического списка (если он "Произвольный" и установлена основная таблица) есть флаг "Динамическое считывание данных". Если он установлен, запрос забирает данные маленькими порциями (например, по 45 строк), что может вызывать подвисание при прокрутке. Если флаг не установлен, порции данных больше (например, по 1000 строк), что обеспечивает более комфортную прокрутку, но может увеличить время выполнения начального запроса. Рекомендации по установке флага зависят от объема выборки и частоты использования прокрутки. Мы должны экспериментировать с этим флагом для определения оптимального значения в нашей конкретной системе.
  5. Оптимизация отображения ссылочных значений: Если табличное поле отображает данные из произвольного источника (например, из таблицы значений, которую мы заполняем), мы можем при заполнении источника данных получать отдельно ссылочные значения и их строковые представления. Затем в обработчике события ПриВыводеСтроки для ячеек, отображающих ссылочные значения, устанавливать в качестве текста уже полученное строковое представление. Это позволит избежать получения строковых представлений при каждом обновлении табличного поля.

Общие рекомендации по ускорению работы 1С

Помимо рассмотренных специфических решений, существуют общие подходы к оптимизации производительности 1С, которые могут помочь в нашем случае и при работе с системой в целом. Давайте рассмотрим некоторые из них.

  1. Актуализация платформы и конфигурации: Регулярное обновление платформы и конфигурации 1С может приводить к повышению производительности, так как разработчики постоянно вносят оптимизации и исправляют известные "узкие места". Убедитесь, что вы используете наиболее стабильные и производительные версии.
  2. Настройка регламентных и фоновых заданий: Многие фоновые задачи (обновление курсов валют, новостных лент, обработка данных обмена и т.д.) могут потреблять значительные ресурсы сервера и СУБД. Мы должны отключить неиспользуемые или настроить расписание их выполнения на нерабочее время (ночь, выходные), когда нагрузка на систему минимальна.
  3. Тестирование и исправление информационной базы (ТИИ): Регулярное выполнение тестирования и исправления базы данных (в монопольном режиме) с пересчетом итогов, сжатием и реструктуризацией таблиц может помочь улучшить быстродействие и устранить возможные логические или физические ошибки в структуре данных. Это особенно актуально для старых баз.
  4. Переход на клиент-серверный вариант работы: Для баз с большим объемом данных или большим количеством пользователей (более 5-7) переход с файлового на клиент-серверный вариант работы (с использованием SQL Server, PostgreSQL или другой поддерживаемой СУБД) может значительно повысить производительность. Это достигается за счет распределения нагрузки между сервером приложений 1С и сервером баз данных, а также оптимизированной работы с данными.
  5. Оптимизация аппаратного обеспечения и системных настроек: Использование SSD-дисков вместо HDD, достаточного объема оперативной памяти (как на сервере 1С, так и на сервере СУБД), мощных процессоров, а также правильная настройка СУБД (особенно для PostgreSQL) и сети между сервером 1С и SQL-сервером имеют существенное влияние на общую производительность системы. Мы должны убедиться, что наше аппаратное обеспечение соответствует требованиям.
  6. Настройка антивируса: Антивирусное программное обеспечение может замедлять работу 1С, сканируя каждый файл или операцию. Мы рекомендуем настроить исключения для файлов и папок информационной базы 1С (например, .cdb для файловой базы или папки с файлами СУБД) в антивирусе.
  7. Удаление неиспользуемых элементов с формы: Количество элементов на форме напрямую влияет на скорость ее работы. Мы должны стремиться к минимизации элементов, удаляя все лишнее, что не используется, особенно опциональный функционал, который может замедлять работу из-за дополнительных проверок или инициализации.

Применение одного или комбинации этих решений поможет нам значительно ускорить работу формы списка документов и повысить общую производительность системы 1С.

← На главную