Каждый технический специалист, впервые столкнувшийся с архитектурой решений 1С на платформе «1С:Предприятие 8.3» (в частности, 1С:ЗУП 3.1, 1С:ERP или 1С:Комплексная автоматизация), испытывает легкое недоумение при анализе транзакций. Кажется нелогичным, что простое нажатие кнопки «Заполнить» в документе НачислениеЗарплатыИВзносов инициирует непосредственную запись данных в таблицы СУБД, минуя стандартный этап проведения документа. В этой статье мы подробно разберем, почему разработчики фирмы 1С приняли такое архитектурное решение, какую роль играет регистр сведений ГрафикиРаботыПоВидамВремени, с какими проблемами можно столкнуться и как их правильно решать.
Рассмотрим стандартное поведение системы. Обычно документы в 1С работают по классическому принципу: пользователь открывает форму, нажимает кнопку заполнения, система рассчитывает данные в оперативной памяти и выводит их на экран. Запись в базу данных происходит только при явном нажатии кнопки «Записать» или «Провести». Однако в подсистеме расчета зарплаты ЗУП 3.1 логика иная.
При нажатии кнопки «Заполнить» система обращается к серверу и выполняет транзакционную запись в независимый регистр сведений ГрафикиРаботыПоВидамВремени. Это необходимо для обеспечения производительности расчетных механизмов. Выясним причину такого поведения подробнее.
Расчет заработной платы — это крайне ресурсоемкая операция, требующая сопоставления множества плановых и фактических показателей за каждый день расчетного периода. Программе необходимо сопоставить:
Если бы система выполняла все эти соединения динамически в оперативной памяти («на лету») в момент расчета каждого сотрудника, то расчет базы для крупного предприятия с тысячами работников занимал бы долгие часы. Для оптимизации этого процесса разработчики 1С создали плоскую служебную таблицу — регистр сведений ГрафикиРаботыПоВидамВремени.
Записи в этом регистре формируются так называемым методом «по запросу». Когда расчетный механизм подготавливает данные для заполнения документа, он проверяет наличие актуального плоского графика в СУБД для каждого рассчитываемого сотрудника. Если готовых данных нет или они устарели, система мгновенно рассчитывает их и физически записывает в базу данных. Это позволяет последующим тяжелым запросам СУБД быстро соединять таблицы начислений с проиндексированными физическими таблицами времени.
Проанализируем структуру регистра ГрафикиРаботыПоВидамВремени. Одним из ключевых его измерений является ГрафикРаботы. На первый взгляд, это обычная ссылка на справочник графиков сотрудников. Однако данное измерение имеет составной тип данных. В зависимости от условий расчета туда могут записываться разные типы объектов:
Табель, документы работы в выходные или праздничные дни, сверхурочные работы и другие специфические отклонения. В этом случае система генерирует индивидуальный слепок рабочего времени, где в качестве «графика» выступает сам сотрудник.Запись «по запросу» в обход транзакции проведения документа решает проблему производительности, но привносит специфические технические сложности, с которыми часто сталкиваются программисты и системные администраторы.
Это классическая коллизия блокировок или кэширования. Она возникает, когда расчетный механизм пытается записать набор записей в регистр ГрафикиРаботыПоВидамВремени, но в базе данных уже существуют аналогичные ключи (например, из-за незавершенных сеансов, некорректно удаленных документов или при одновременной работе нескольких расчетчиков в одном периоде).
Для устранения этой ошибки разберем несколько шагов:
Поскольку персональные записи генерируются под каждого сотрудника при малейшем отклонении от графика, регистр очень быстро раздувается. В базах крупных холдингов количество записей в нем может превышать 10–15 миллионов строк. Это приводит к значительному увеличению размера файла базы данных (или таблиц на SQL-сервере), замедлению процессов резервного копирования и потенциальным ошибкам нехватки памяти (Out of Memory) при проведении тестирования и исправления.
Если пользователь нажал кнопку «Заполнить», система сгенерировала и записала плоские графики в регистр, но затем пользователь решил закрыть документ без сохранения. В этот момент временные записи графиков остаются в базе данных. В большинстве случаев это не мешает работе, так как при следующем расчете данные перезапишутся, однако в редких случаях это может приводить к временным расхождениям в аналитических отчетах по рабочему времени.
Как совершенно верно отметил один из участников обсуждения на форуме, данные в этом регистре носят подчиненный, технологический характер. Это означает, что полное программное удаление записей из регистра ГрафикиРаботыПоВидамВремени не приведет к потере ведения учета или поломке базы данных.
При первой же необходимости (например, при расчете аванса, заполнении нового документа начисления зарплаты или расчете среднего заработка для отпуска) система автоматически обнаружит отсутствие записей и сгенерирует их заново. Тем не менее, делать полную очистку в разгар рабочего дня в крупной компании настоятельно не рекомендуется.
Рассмотрим по шагам последствия тотальной очистки регистра в рабочей базе:
Если вам все же необходимо оптимизировать базу данных и удалить устаревшие записи (например, по сотрудникам, уволенным несколько лет назад), делать это нужно аккуратно и программно. Ниже приведем пример кода, который демонстрирует, как можно выборочно очистить данные по конкретному графику или периоду регистрации:
// Пример выборочного удаления устаревших записей регистра
Процедура ОчиститьУстаревшиеГрафики(ГрафикДляОчистки, ПериодНачала) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| ГрафикиРаботыПоВидамВремени.ПериодРегистрации КАК ПериодРегистрации,
| ГрафикиРаботыПоВидамВремени.ГрафикРаботы КАК ГрафикРаботы
|ИЗ
| РегистрСведений.ГрафикиРаботыПоВидамВремени КАК ГрафикиРаботыПоВидамВремени
|ГДЕ
| ГрафикиРаботыПоВидамВремени.ГрафикРаботы = &ГрафикРаботы
| И ГрафикиРаботыПоВидамВремени.ПериодРегистрации < &ПериодНачала";
Запрос.УстановитьПараметр("ГрафикРаботы", ГрафикДляОчистки);
Запрос.УстановитьПараметр("ПериодНачала", ПериодНачала);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Создаем набор записей с жестким отбором для очистки
НаборЗаписей = РегистрыСведений.ГрафикиРаботыПоВидамВремени.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.ПериодРегистрации.Установить(Выборка.ПериодРегистрации);
НаборЗаписей.Отбор.ГрафикРаботы.Установить(Выборка.ГрафикРаботы);
// Записываем пустой набор для удаления записей из СУБД
НаборЗаписей.Записать(Истина);
КонецЦикла;
КонецПроцедуры
Использование транзакционной очистки небольшими порциями гарантирует, что СУБД не заблокирует всю таблицу целиком, а пользователи не заметят снижения производительности системы.
Таким образом, запись в базу данных по кнопке «Заполнить» в 1С:ЗУП 3.1 — это вынужденное, но полностью оправданное с точки зрения производительности архитектурное решение. Понимание принципа работы этого механизма убережет вас от паники при анализе технологического журнала и позволит оперативно устранять возникающие коллизии при расчете заработной платы.