При работе с файловыми базами данных «1С:Предприятие 8.2» на конфигурации «Бухгалтерия предприятия 2.0» пользователи часто сталкиваются с резким падением производительности. Характерный симптом: при открытии, проведении или записи банковских документов система «зависает» на несколько секунд, а сетевой трафик между клиентским компьютером и сервером возрастает до предела. Особенно остро эта проблема проявляется при увеличении объема базы данных (более 1 Гб) и росте аналитики по субконто.
В этой статье мы подробно разберем причины возникновения таких зависаний, проанализируем поведение СУБД и файловой системы при работе по сети, а также рассмотрим практические способы оптимизации кода и инфраструктуры для восстановления быстродействия системы.
Для того чтобы понять, почему база данных зависает на 5–8 секунд при работе с банковскими выписками, выполним замер производительности в отладчике «1С:Предприятия». Проанализировав технологический журнал и результаты замера, мы увидим, что основное время выполнения уходит на работу формы списка журнала документов БанковскиеВыписки.
Аномально долго выполняется типовая функция ПолучитьИтоги(ТекДата, ТекБанковскийСчет), которая призвана динамически рассчитывать остатки и обороты на выбранную пользователем дату по конкретному расчетному счету. При скроллинге журнала или переходе с одного дня на другой эта функция вызывается повторно, заставляя интерфейс полностью «замирать».
Рассмотрим исходный текст запроса функции ПолучитьИтоги, который поставляется в типовой конфигурации:
Запрос.Текст = "ВЫБРАТЬ РАЗРЕШЕННЫЕ
| ВЫБОР
| КОГДА БанковскиеСчета.ВалютаДенежныхСредств = &ВалютаРеглУчета
| ТОГДА ХозрасчетныйОстаткиИОбороты.СуммаНачальныйОстаток
| ИНАЧЕ ХозрасчетныйОстаткиИОбороты.ВалютнаяСуммаНачальныйОстаток
| КОНЕЦ КАК НачальныйОстаток,
| ВЫБОР
| КОГДА БанковскиеСчета.ВалютаДенежныхСредств = &ВалютаРеглУчета
| ТОГДА ХозрасчетныйОстаткиИОбороты.СуммаОборотДт
| ИНАЧЕ ХозрасчетныйОстаткиИОбороты.ВалютнаяСуммаОборотДт
| КОНЕЦ КАК ВсегоПоступило,
| ВЫБОР
| КОГДА БанковскиеСчета.ВалютаДенежныхСредств = &ВалютаРеглУчета
| ТОГДА ХозрасчетныйОстаткиИОбороты.СуммаОборотКт
| ИНАЧЕ ХозрасчетныйОстаткиИОбороты.ВалютнаяСуммаОборотКт
| КОНЕЦ КАК ВсегоСписано,
| ВЫБОР
| КОГДА БанковскиеСчета.ВалютаДенежныхСредств = &ВалютаРеглУчета
| ТОГДА ХозрасчетныйОстаткиИОбороты.СуммаКонечныйОстаток
| ИНАЧЕ ХозрасчетныйОстаткиИОбороты.ВалютнаяСуммаКонечныйОстаток
| КОНЕЦ КАК КонечныйОстаток
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.ОстаткиИОбороты(
| НАЧАЛОПЕРИОДА(&ДатаИтогов, ДЕНЬ),
| КОНЕЦПЕРИОДА(&ДатаИтогов, ДЕНЬ),
| ,
| ДвиженияИГраницыПериода,
| ,
| ЗНАЧЕНИЕ(ПланВидовХарактеристик.ВидыСубконтоХозрасчетные.БанковскиеСчета),
| Организация = &Организация
| И Субконто1 = &БанковскийСчетИтогов) КАК ХозрасчетныйОстаткиИОбороты
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.БанковскиеСчета КАК БанковскиеСчета
| ПО ХозрасчетныйОстаткиИОбороты.Subkonto1 = БанковскиеСчета.Ссылка
|ГДЕ
| БанковскиеСчета.Ссылка = &БанковскийСчетИтогов";
Проанализируем параметры виртуальной таблицы РегистрБухгалтерии.Хозрасчетный.ОстаткиИОбороты. Мы видим, что в качестве параметров отбора передаются Организация и значение субконто (Субконто1 = &БанковскийСчетИтогов). Однако параметр Счет в условиях виртуальной таблицы полностью отсутствует!
С точки зрения СУБД (как файлового движка .1CD, так и внешних СУБД вроде MS SQL или PostgreSQL), это критическая ошибка построения запроса по следующим причинам:
Счет (_AccountRRef) идет первым. Если в запросе не указан конкретный счет (например, 51 «Расчетные счета» или 52 «Валютные счета»), СУБД не может выполнить быстрое позиционирование по индексу (операцию Index Seek).РегистрБухгалтерии.Хозрасчетный и РегистрБухгалтерии.Хозрасчетный.ИтогиПоСчетамССубконто разрастаются до огромных размеров. Полное сканирование таких таблиц при каждом открытии выписки парализует работу СУБД.Для устранения этой проблемы нам необходимо жестко ограничить выборку виртуальной таблицы конкретным счетом учета. Для этого отредактируем функцию ПолучитьИтоги. Мы добавим в параметры виртуальной таблицы отбор по счету Счет.Ссылка = &СчетБанк. Передаваемое значение мы будем получать из текущей строки журнала документов.
Рассмотрим оптимизированный вариант функции:
Функция ПолучитьИтоги(ТекДата, ТекБанковскийСчет)
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ РАЗРЕШЕННЫЕ
| ВЫБОР
| КОГДА БанковскиеСчета.ВалютаДенежныхСредств = &ВалютаРеглУчета
| ТОГДА ХозрасчетныйОстаткиИОбороты.СуммаНачальныйОстаток
| ИНАЧЕ ХозрасчетныйОстаткиИОбороты.ВалютнаяСуммаНачальныйОстаток
| КОНЕЦ КАК НачальныйОстаток,
| ВЫБОР
| КОГДА БанковскиеСчета.ВалютаДенежныхСредств = &ВалютаРеглУчета
| ТОГДА ХозрасчетныйОстаткиИОбороты.СуммаОборотДт
| ИНАЧЕ ХозрасчетныйОстаткиИОбороты.ВалютнаяСуммаОборотДт
| КОНЕЦ КАК ВсегоПоступило,
| ВЫБОР
| КОГДА БанковскиеСчета.ВалютаДенежныхСредств = &ВалютаРеглУчета
| ТОГДА ХозрасчетныйОстаткиИОбороты.СуммаОборотКт
| ИНАЧЕ ХозрасчетныйОстаткиИОбороты.ВалютнаяСуммаОборотКт
| КОНЕЦ КАК ВсегоСписано,
| ВЫБОР
| КОГДА БанковскиеСчета.ВалютаДенежныхСредств = &ВалютаРеглУчета
| ТОГДА ХозрасчетныйОстаткиИОбороты.СуммаКонечныйОстаток
| ИНАЧЕ ХозрасчетныйОстаткиИОбороты.ВалютнаяСуммаКонечныйОстаток
| КОНЕЦ КАК КонечныйОстаток
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.ОстаткиИОбороты(
| НАЧАЛОПЕРИОДА(&ДатаИтогов, ДЕНЬ),
| КОНЕЦПЕРИОДА(&ДатаИтогов, ДЕНЬ),
| ,
| ДвиженияИГраницыПериода,
| Счет.Ссылка = &СчетБанк,
| ЗНАЧЕНИЕ(ПланВидовХарактеристик.ВидыСубконтоХозрасчетные.БанковскиеСчета),
| Организация = &Организация
| И Субконто1 = &БанковскийСчетИтогов) КАК ХозрасчетныйОстаткиИОбороты
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.БанковскиеСчета КАК БанковскиеСчета
| ПО ХозрасчетныйОстаткиИОбороты.Субконто1 = БанковскиеСчета.Ссылка
|ГДЕ
| БанковскиеСчета.Ссылка = &БанковскийСчетИтогов";
Запрос.УстановитьПараметр("ДатаИтогов", ТекДата);
Запрос.УстановитьПараметр("БанковскийСчетИтогов", ТекБанковскийСчет);
// Передаем счет учета из текущей строки журнала документов
Попытка
ТекущийСчетУчета = ЭлементыФормы.ЖурналДокументовСписок.ТекущаяСтрока.СчетБанк;
Исключение
ТекущийСчетУчета = ПланыСчетов.Хозрасчетный.РасчетныеСчета;
КонецПопытки;
Запрос.УстановитьПараметр("СчетБанк", ТекущийСчетУчета);
Запрос.УстановитьПараметр("Организация", ТекБанковскийСчет.Владелец);
Запрос.УстановитьПараметр("ВалютаРеглУчета", мВалютаРегламентированногоУчета);
ТаблицаИтогов = Запрос.Выполнить.Выгрузить;
Возврат ТаблицаИтогов;
КонецФункции
Благодаря явному указанию параметра СчетБанк в условиях виртуальной таблицы, СУБД моментально отсекает лишние ветки индекса. Вместо сканирования миллионов записей система делает точечную выборку (Index Seek), снижая время выполнения запроса с 5 секунд до нескольких миллисекунд.
Почему при работе одного пользователя локально тормоза практически незаметны, а при подключении второго пользователя по сети 100 Мбит/с работа становится невозможной? Разберем физику этого процесса.
Когда с базой работает один пользователь, операционная система Windows применяет механизм **OpLocks (оппортунистических блокировок)**. Файл базы данных 1Cv8.1CD кэшируется в оперативной памяти клиентского ПК. Все тяжелые запросы выполняются внутри быстрой локальной памяти.
Как только к базе подключается **второй пользователь**, сетевой кэш принудительно отключается операционной системой во избежание повреждения структуры базы данных. С этого момента любой запрос начинает напрямую считывать бинарные страницы файла базы по сети. Неоптимальный запрос, выполняющий сканирование индексов, заставляет платформу прокачивать сотни мегабайт «сырых» данных. Сетевой адаптер 100 Мбит/с мгновенно утилизируется на 70-90%, создавая огромные задержки («очередь к диску» и сетевые коллизии).
Еще одной неочевидной причиной медленной работы виртуальных таблиц является **неактуальность рассчитанных итогов регистра бухгалтерии**. Выясним, почему это происходит:
Если оптимизация кода запроса дала положительный эффект, но общая производительность файловой базы оставляет желать лучшего, проанализируем архитектурные решения:
Применяя данные методы оптимизации запросов и администрирования в комплексе, мы добьемся мгновенного отклика журнала банковских выписок и обеспечим стабильную и быструю работу пользователей в программе.