Приветствуем вас! В этой статье мы подробно разберем одну из самых неприятных и труднодиагностируемых проблем в программе 1С:Зарплата и управление персоналом, редакция 3.1. Представьте ситуацию: каждый месяц в начале расчетного периода при попытке рассчитать заработную плату база данных намертво зависает. Процессор сервера нагружается на 100%, пользователи не могут работать, а сам процесс расчета затягивается на долгие часы или даже сутки. При этом в течение остального месяца программа работает быстро и без нареканий.
Давайте разберемся по шагам, выясним технические и бизнес-причины такого поведения системы, проанализируем тяжелый запрос СУБД, а также рассмотрим несколько эффективных способов решения этой проблемы — от быстрых настроек в пользовательском режиме до оптимизации на уровне SQL и кода.
Для начала проанализируем, на каком именно этапе происходит зависание. С помощью замера производительности в конфигураторе легко обнаружить, что система «застревает» на выполнении процедуры в общем модуле ЗарплатаКадрыПериодическиеРегистры.ВыполнитьЗапросПолученияСрезаРегистра. Эта процедура отвечает за получение среза последних данных кадровой истории сотрудников на определенные даты.
Начиная с редакции ЗУП 3.1, разработчики фирмы «1С» отказались от классического использования стандартных виртуальных таблиц СУБД (таких как СрезПоследних) в пользу так называемого механизма представлений. Теперь, когда программа обращается к кадровой истории, платформа на лету генерирует огромный и сложный SQL-запрос с множеством временных таблиц и левых соединений. Ниже представлен тот самый запрос, на котором СУБД блокирует работу сервера:
ВЫБРАТЬ РАЗРЕШЕННЫЕ
РегистрСведений.Период КАК Период,
ВЫБОР
КОГДА РегистрСведений.ДействуетДо = ДАТАВРЕМЯ(1, 1, 1)
ТОГДА ДАТАВРЕМЯ(3999, 12, 31, 23, 59, 59)
ИНАЧЕ РегистрСведений.ДействуетДо
КОНЕЦ КАК ДействуетДо,
РегистрСведений.Сотрудник КАК Сотрудник,
РегистрСведений.ГоловнаяОрганизация КАК ГоловнаяОрганизация,
РегистрСведений.ФизическоеЛицо КАК ФизическоеЛицо
ПОМЕСТИТЬ ВТРегистрСведений
ИЗ
ВТПодразделенияПериоды КАК ИзмеренияДаты
Внутреннее СОЕДИНЕНИЕ РегистрСведений.КадроваяИсторияСотрудников КАК РегистрСведений
ПО ((РегистрСведений.Период <=
ВЫБОР
КОГДА ИзмеренияДаты.Период = ДАТАВРЕМЯ(1, 1, 1)
ТОГДА ДАТАВРЕМЯ(3999, 12, 31, 23, 59, 59)
ИНАЧЕ КОНЕЦПЕРИОДА(ИзмеренияДаты.Период,ДЕНЬ)
КОНЕЦ))
И (РегистрСведений.Подразделение В (&ВТКадроваяИсторияСотрудниковСрезПоследних_Параметр1))
;
ВЫБРАТЬ РАЗРЕШЕННЫЕ РАЗЛИЧНЫЕ
РегистрСведений.Период КАК Период,
ВЫБОР
КОГДА РегистрСведений.Период < РегистрСведенийПересечения.Период
И РегистрСведений.ДействуетДо > РегистрСведенийПересечения.Период
ТОГДА ДАТАВРЕМЯ(3999, 12, 31, 23, 59, 59)
ИНАЧЕ РегистрСведений.ДействуетДо
КОНЕЦ КАК ДействуетДо,
РегистрСведений.Сотрудник КАК Сотрудник,
РегистрСведений.ГоловнаяОрганизация КАК ГоловнаяОрганизация,
РегистрСведений.ФизическоеЛицо КАК ФизическоеЛицо
ПОМЕСТИТЬ ВТПересеченияРегистров
ИЗ
ВТРегистрСведений КАК РегистрСведений
Левое внешнее СОЕДИНЕНИЕ ВТРегистрСведений КАК РегистрСведенийПересечения
ПО ((РегистрСведений.Период < РегистрСведенийПересечения.Период))
И ((РегистрСведений.ДействуетДо > RegisterСведенийПересечения.Период))
И ((РегистрСведений.Сотрудник = РегистрСведенийПересечения.Сотрудник))
И ((РегистрСведений.ГоловнаяОрганизация = РегистрСведенийПересечения.ГоловнаяОрганизация))
И ((РегистрСведений.ФизическоеЛицо = РегистрСведенийПересечения.ФизическоеЛицо))
;
ВЫБРАТЬ РАЗРЕШЕННЫЕ
МАКСИМУМ(РегистрСведений.Период) КАК МаксимальныйПериодИзмерения,
ИзмеренияДаты.Период КАК ЗаданныйПериод,
МАКСИМУМ(ВЫБОР
КОГДА
ВЫБОР
КОГДА ИзмеренияДаты.Период = ДАТАВРЕМЯ(1, 1, 1)
ТОГДА ДАТАВРЕМЯ(3999, 12, 31, 23, 59, 59)
ИНАЧЕ КОНЕЦПЕРИОДА(ИзмеренияДаты.Период,ДЕНЬ)
КОНЕЦ <= РегистрСведений.ДействуетДо
ТОГДА РегистрСведений.Период
ИНАЧЕ ДАТАВРЕМЯ(1, 1, 1)
КОНЕЦ) КАК МаксимальныйПериодРесурсы,
РегистрСведений.Сотрудник КАК Сотрудник,
РегистрСведений.ГоловнаяОрганизация КАК ГоловнаяОрганизация,
РегистрСведений.ФизическоеЛицо КАК ФизическоеЛицо
ПОМЕСТИТЬ ВТМаксимальныеПериоды
ИЗ
ВТПодразделенияПериоды КАК ИзмеренияДаты
Внутреннее СОЕДИНЕНИЕ ВТПересеченияРегистров КАК РегистрСведений
ПО ((
ВЫБОР
КОГДА ИзмеренияДаты.Период = ДАТАВРЕМЯ(1, 1, 1)
ТОГДА ДАТАВРЕМЯ(3999, 12, 31, 23, 59, 59)
ИНАЧЕ КОНЕЦПЕРИОДА(ИзмеренияДаты.Период,ДЕНЬ)
КОНЕЦ >= РегистрСведений.Период))
СГРУППИРОВАТЬ ПО
ИзмеренияДаты.Период,
РегистрСведений.Сотрудник,
РегистрСведений.ГоловнаяОрганизация,
РегистрСведений.ФизическоеЛицо
;
ВЫБРАТЬ РАЗРЕШЕННЫЕ
ИзмеренияДаты.ЗаданныйПериод КАК Период,
РегистрСведенийИзмерения.Период КАК ПериодЗаписи,
ВЫБОР
КОГДА ИзмеренияДаты.МаксимальныйПериодИзмерения = ИзмеренияДаты.МаксимальныйПериодРесурсы
ТОГДА ЛОЖЬ
ИНАЧЕ ИСТИНА
КОНЕЦ КАК ЭтоВозвратноеСобытие,
РегистрСведенийИзмерения.ДействуетДо КАК ПериодВозвратногоСобытия,
РегистрСведенийИзмерения.Регистратор КАК Регистратор,
РегистрСведенийИзмерения.Регистратор КАК РегистраторЗаписи,
ИзмеренияДаты.Сотрудник КАК Сотрудник,
ИзмеренияДаты.ГоловнаяОрганизация КАК ГоловнаяОрганизация,
ИзмеренияДаты.ФизическоеЛицо КАК ФизическоеЛицо,
ВЫБОР
КОГДА ИзмеренияДаты.МаксимальныйПериодРесурсы <> ДАТАВРЕМЯ(1, 1, 1)
ТОГДА РегистрСведенийРесурсы.Организация
ИНАЧЕ РегистрСведенийИзмерения.Организация
КОНЕЦ КАК Организация,
ВЫБОР
КОГДА ИзмеренияДаты.МаксимальныйПериодРесурсы <> ДАТАВРЕМЯ(1, 1, 1)
ТОГДА РегистрСведенийРесурсы.Подразделение
ИНАЧЕ РегистрСведенийИзмерения.Подразделение
КОНЕЦ КАК Подразделение,
ВЫБОР
КОГДА ИзмеренияДаты.МаксимальныйПериодРесурсы <> ДАТАВРЕМЯ(1, 1, 1)
ТОГДА РегистрСведенийРесурсы.Должность
ИНАЧЕ РегистрСведенийИзмерения.Должность
КОНЕЦ КАК Должность,
ВЫБОР
КОГДА ИзмеренияДаты.МаксимальныйПериодРесурсы <> ДАТАВРЕМЯ(1, 1, 1)
ТОГДА РегистрСведенийРесурсы.ДолжностьПоШтатномуРасписанию
ИНАЧЕ РегистрСведенийИзмерения.ДолжностьПоШтатномуРасписанию
КОНЕЦ КАК ДолжностьПоШтатномуРасписанию,
ВЫБОР
КОГДА ИзмеренияДаты.МаксимальныйПериодРесурсы <> ДАТАВРЕМЯ(1, 1, 1)
ТОГДА РегистрСведенийРесурсы.КоличествоСтавок
ИНАЧЕ РегистрСведенийИзмерения.КоличествоСтавок
КОНЕЦ КАК КоличествоСтавок,
ВЫБОР
КОГДА ВЫБОР
КОГДА ИзмеренияДаты.МаксимальныйПериодИзмерения = ИзмеренияДаты.МаксимальныйПериодРесурсы
ТОГДА ЛОЖЬ
ИНАЧЕ ИСТИНА
КОНЕЦ
И
ВЫБОР
КОГДА ИзмеренияДаты.МаксимальныйПериодРесурсы <> ДАТАВРЕМЯ(1, 1, 1)
ТОГДА РегистрСведенийРесурсы.ВидСобытия
ИНАЧЕ &ПараметрыНаследованияВидСобытия
КОНЕЦ В (ЗНАЧЕНИЕ(Перечисление.ВидыКадровыхСобытий.Прием), ЗНАЧЕНИЕ(Перечисление.ВидыКадровыхСобытий.НачальныеДанные))
ТОГДА ЗНАЧЕНИЕ(Перечисление.ВидыКадровыхСобытий.Перемещение)
ИНАЧЕ
ВЫБОР
КОГДА ИзмеренияДаты.МаксимальныйПериодРесурсы <> ДАТАВРЕМЯ(1, 1, 1)
ТОГДА РегистрСведенийРесурсы.ВидСобытия
ИНАЧЕ &ПараметрыНаследованияВидСобытия
КОНЕЦ
КОНЕЦ КАК ВидСобытия,
ВЫБОР
КОГДА ИзмеренияДаты.МаксимальныйПериодРесурсы <> ДАТАВРЕМЯ(1, 1, 1)
ТОГДА РегистрСведенийРесурсы.ВидДоговора
ИНАЧЕ РегистрСведенийИзмерения.ВидДоговора
КОНЕЦ КАК ВидДоговора,
ВЫБОР
КОГДА ИзмеренияДаты.МаксимальныйПериодРесурсы <> ДАТАВРЕМЯ(1, 1, 1)
ТОГДА РегистрСведенийРесурсы.ГоловнойСотрудник
ИНАЧЕ РегистрСведенийИзмерения.ГоловнойСотрудник
КОНЕЦ КАК ГоловнойСотрудник,
ВЫБОР
КОГДА ИзмеренияДаты.МаксимальныйПериодРесурсы <> ДАТАВРЕМЯ(1, 1, 1)
ТОГДА РегистрСведенийРесурсы.ЭтоГоловнойСотрудник
ИНАЧЕ РегистрСведенийИзмерения.ЭтоГоловнойСотрудник
КОНЕЦ КАК ЭтоГоловнойСотрудник
ПОМЕСТИТЬ ВТКадроваяИсторияСотрудниковСрезПоследних
ИЗ
ВТМаксимальныеПериоды КАК ИзмеренияДаты
Левое внешнее СОЕДИНЕНИЕ РегистрСведений.КадроваяИсторияСотрудников КАК РегистрСведенийИзмерения
ПО ((ИзмеренияДаты.МаксимальныйПериодИзмерения = РегистрСведенийИзмерения.Период))
И ((ИзмеренияДаты.Сотрудник = РегистрСведенийИзмерения.Сотрудник))
И ((ИзмеренияДаты.ГоловнаяОрганизация = РегистрСведенийИзмерения.ГоловнаяОрганизация))
И ((ИзмеренияДаты.ФизическоеЛицо = РегистрСведенийИзмерения.ФизическоеЛицо))
Левое внешнее СОЕДИНЕНИЕ РегистрСведений.КадроваяИсторияСотрудников КАК РегистрСведенийРесурсы
ПО ((ИзмеренияДаты.МаксимальныйПериодРесурсы = РегистрСведенийРесурсы.Период))
I ((ИзмеренияДаты.Сотрудник = РегистрСведенийРесурсы.Сотрудник))
И ((ИзмеренияДаты.ГоловнаяОрганизация = РегистрСведенийРесурсы.ГоловнаяОрганизация))
И ((ИзмеренияДаты.ФизическоеЛицо = РегистрСведенийРесурсы.ФизическоеЛицо))
;
УНИЧТОЖИТЬ ВТРегистрСведений
;
УНИЧТОЖИТЬ ВТПересеченияРегистров
;
Проанализируем этот запрос. Мы видим множественные левые соединения временных таблиц по непрямым условиям (например, РегистрСведений.Период < РегистрСведенийПересечения.Период) и сложные вычисления условий ВЫБОР КОГДА. Если во временную таблицу ВТПодразделенияПериоды попадает большой объем данных, СУБД (будь то Microsoft SQL Server или PostgreSQL) перестает справляться с оптимальным построением плана выполнения запроса. Происходит так называемый Parameter Sniffing — оптимизатор СУБД строит план выполнения на основе пустых или маленьких таблиц, а когда в них передается огромный массив строк, начинает использовать неэффективные алгоритмы (например, *Nested Loops* вместо *Hash Match*). Это приводит к лавинообразному росту чтений и нагрузке на процессоры сервера до предела.
Почему же эта критическая ситуация возникает только раз в месяц в начале расчетного периода? Давайте выясним реальную причину на уровне бизнес-логики ведения учета.
Рассмотрим распространенный сценарий ведения расчета на крупных предприятиях (например, с количеством сотрудников от 2500 человек и наличием около 80-100 подразделений):
Начисление заработной платы.Данные для расчета зарплаты по всем подразделениям сразу.Начисление заработной платы программа обращается к этому регистру, чтобы понять, кому нужно пересчитать данные. Запускается тяжелое регламентное задание Регистрация перерасчетов, которое начинает вычислять кадровую историю сотрудников по сложным срезам, используя приведенный выше запрос. Система «зависает» на часы или сутки, пытаясь переварить огромный объем данных.Если вы хотите детально исследовать поведение этого запроса на вашей СУБД, вам потребуются реальные данные временных таблиц из оперативной памяти в момент зависания. Сделать это стандартными средствами непросто, так как менеджер временных таблиц уничтожается после завершения транзакции.
Для решения этой задачи воспользуемся удобным отладочным методом. Добавим в расширение конфигурации специальный общий серверный модуль и будем вызывать процедуру экспорта таблиц прямо из табло отладчика:
// Эту функцию можно вызывать в окне отладки, что позволит получить временнные таблицы на диске для использования во внешнем запросе
Функция ПолучитьЗапросВТ(КаталогВТ) Экспорт
мФайлы = Новый Массив;
Для каждого тФайл Из НайтиФайлы(КаталогВТ, "*.sv") Цикл
мФайлы.Добавить(тФайл);
КонецЦикла;
Если НЕ мФайлы.Количество() Тогда
Сообщить("Не обнаружены файлы *.sv" + " в каталоге " + КаталогВТ);
Возврат "";
КонецЕсли;
мТекстыПакетов = Новый Массив;
Для каждого тФайл Из мФайлы Цикл
тТз = ЗначениеИзФайла(тФайл.ПолноеИмя);
Если ТипЗнч(тТз) <> Тип("ТаблицаЗначений") Тогда
Сообщить("Тип данных не таблица значений в файле: " + тФайл.ПолноеИмя);
Продолжить;
КонецЕсли;
Если Ложь Тогда
тТз = Новый ТаблицаЗначений;
КонецЕсли;
мСтрокиПолей = Новый Массив;
Для каждого тКолонка Из тТз.Колонки Цикл
КолонкаНеСоставногоТипаТип = Неопределено;
Если тКолонка.ТипЗначения.Типы().Количество() = 1 Тогда
КолонкаНеСоставногоТипаТип = тКолонка.ТипЗначения.Типы()[0];
ИначеЕсли тКолонка.ТипЗначения.Типы().Количество() = 2 Тогда
Если тКолонка.ТипЗначения.Типы()[0] <> ТипЗнч(Null) Тогда
КолонкаНеСоставногоТипаТип = тКолонка.ТипЗначения.Типы()[0];
ИначеЕсли тКолонка.ТипЗначения.Типы()[1] <> ТипЗнч(Null) Тогда
КолонкаНеСоставногоТипаТип = тКолонка.ТипЗначения.Типы()[1];
КонецЕсли;
КонецЕсли;
// Во временных таблицах желательно выражать тип поля для исключения ошибки доступа через точку
Если Метаданные.Справочники.Найти("КлючиАналитикиУчетаНаборов") <> Неопределено
И/ИЛИ КолонкаНеСоставногоТипаТип = Тип("СправочникСсылка.КлючиАналитикиУчетаНаборов") Тогда
СтрокаПоля = "ВЫРАЗИТЬ(Т."+тКолонка.Имя+" КАК Справочник.КлючиАналитикиУчетаНаборов)";
ИначеЕсли Метаданные.Справочники.Найти("КлючиАналитикиУчетаНоменклатуры") <> Неопределено
И КолонкаНеСоставногоТипаТип = Тип("СправочникСсылка.КлючиАналитикиУчетаНоменклатуры") Тогда
СтрокаПоля = "ВЫРАЗИТЬ(Т."+тКолонка.Имя+" КАК Справочник.КлючиАналитикиУчетаНоменклатуры)";
ИначеЕсли Метаданные.Справочники.Найти("КлючиАналитикиУчетаПартий") <> Неопределено
И КолонкаНеСоставногоТипаТип = Тип("СправочникСсылка.КлючиАналитикиУчетаПартий") Тогда
СтрокаПоля = "ВЫРАЗИТЬ(Т."+тКолонка.Имя+" КАК Справочник.КлючиАналитикиУчетаПартий)";
ИначеЕсли Метаданные.Справочники.Найти("КлючиАналитикиУчетаПо Партнерам") <> Неопределено
И КолонкаНеСоставногоТипаТип = Тип("СправочникСсылка.КлючиАналитикиУчетаПоПартнерам") Тогда
СтрокаПоля = "ВЫРАЗИТЬ(Т."+тКолонка.Имя+" КАК Справочник.КлючиАналитикиУчетаПоПартнерам)";
ИначеЕсли Метаданные.Справочники.Найти("ДоговорыКонтрагентов") <> Неопределено
И КолонкаНеСоставногоТипаТип = Тип("СправочникСсылка.ДоговорыКонтрагентов") Тогда
СтрокаПоля = "ВЫРАЗИТЬ(Т."+тКолонка.Имя+" КАК Справочник.ДоговорыКонтрагентов)";
ИначеЕсли Метаданные.Справочники.Найти("Контрагенты") <> Неопределено
И КолонкаНеСоставногоТипаТип = Тип("СправочникСсылка.Контрагенты") Тогда
СтрокаПоля = "ВЫРАЗИТЬ(Т."+тКолонка.Имя+" КАК Справочник.Контрагенты)";
ИначеЕсли Метаданные.Справочники.Найти("Партнеры") <> Неопределено
И КолонкаНеСоставногоТипаТип = Тип("СправочникСсылка.Партнеры") Тогда
СтрокаПоля = "ВЫРАЗИТЬ(Т."+тКолонка.Имя+" КАК Справочник.Партнеры)";
ИначеЕсли Метаданные.Справочники.Найти("Номенклатура") <> Неопределено
И КолонкаНеСоставногоТипаТип = Тип("СправочникСсылка.Номенклатура") Тогда
СтрокаПоля = "ВЫРАЗИТЬ(Т."+тКолонка.Имя+" КАК Справочник.Номенклатура)";
Иначе
СтрокаПоля = "Т."+тКолонка.Имя;
КонецЕсли;
мСтрокиПолей.Добавить(" "+СтрокаПоля+" КАК "+тКолонка.Имя);
КонецЦикла;
мСтрокиПакета = Новый Массив;
мСтрокиПакета.Добавить("ВЫБРАТЬ");
мСтрокиПакета.Добавить(СтрСоединить(мСтрокиПолей, ","+Символы.ПС));
мСтрокиПакета.Добавить("ПОМЕСТИТЬ " + тФайл.ИмяБезРасширения);
мСтрокиПакета.Добавить("ИЗ");
мСтрокиПакета.Добавить(" &"+тФайл.ИмяБезРасширения+" КАК Т");
мТекстыПакетов.Добавить(СтрСоединить(мСтрокиПакета, Символы.ПС));
КонецЦикла;
ТекстЗапроса = СтрСоединить(мТекстыПакетов, Символы.ПС+";"+Символы.ПС+Символы.ПС);
Возврат ТекстЗапроса;
КонецФункции
Функция ПолучитьВТ(КаталогВТ, Запрос=Неопределено, Мвт=Неопределено) Экспорт
УдалитьФайлы(КаталогВТ,"*.*");
Если Запрос = Неопределено Тогда
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Мвт;
КонецЕсли;
Если Запрос.МенеджерВременныхТаблиц = Неопределено Тогда
Возврат "";
КонецЕсли;
Если НЕ Запрос.МенеджерВременныхТаблиц.Таблицы.Количество() Тогда
Возврат "";
КонецЕсли;
ствВт = Новый Соответствие;
ЗапросТекстДляПоиска = ВРег(СтрЗаменить(Запрос.Текст," ",""));
Для каждого Вт Из Запрос.МенеджерВременныхТаблиц.Таблицы Цикл
Если НЕ ПустаяСтрока(ЗапросТекстДляПоиска) Тогда
Если СтрНайти(ЗапросТекстДляПоиска, "ПОМЕСТИТЬ" + ВРег(Вт.ПолноеИмя)) Тогда
Продолжить;
КонецЕсли;
Если НЕ
СтрНайти(ЗапросТекстДляПоиска, ВРег(Вт.ПолноеИмя) + ".") +
СтрНайти(ЗапросТекстДляПоиска, ВРег(Вт.ПолноеИмя) + "КАК")
Тогда
Продолжить;
КонецЕсли;
КонецЕсли;
ствВт.Вставить(Вт.ПолноеИмя, Вт);
КонецЦикла;
Для каждого Эл Из ствВт Цикл
ЗначениеВФайл(КаталогВТ+"\"+Эл.Ключ+".sv", Эл.Значение.ПолучитьДанные().Выгрузить());
КонецЦикла;
ЗапросТекстВт = ПолучитьЗапросВТ(КаталогВТ);
Если ПустаяСтрока(ЗапросТекстВт) Тогда
ЗапросТекстДляКоносли = Запрос.Текст;
Иначе
ЗапросТекстДляКоносли = ЗапросТекстВт + Символы.ПС + ";" + Символы.ПС + Запрос.Текст;
КонецЕсли;
ТекстовыйДок = Новый ТекстовыйДокумент;
ТекстовыйДок.УстановитьТекст(ЗапросТекстДляКоносли);
ТекстовыйДок.Записать(КаталогВТ+"\"+"_ТекстЗапроса.txt", КодировкаТекста.UTF8);
ТекстовыйДок.Очистить();
Если Запрос.Параметры.Количество() Тогда
Для каждого тПараметр Из Запрос.Параметры Цикл
мИнфоПараметра = Новый Массив;
мИнфоПараметра.Добавить(тПараметр.Ключ);
мИнфоПараметра.Добавить(Строка(тПараметр.Значение));
мИнфоПараметра.Добавить(ТипЗнч(тПараметр.Значение));
ТекстовыйДок.ДобавитьСтроку(СтрСоединить(мИнфоПараметра, " ~ "));
КонецЦикла;
ТекстовыйДок.Записать(КаталогВТ+"\"+"_ПараметрыЗапроса.txt", КодировкаТекста.UTF8);
КонецЕсли;
Возврат ЗапросТекстДляКоносли;
КонецФункции
Для использования этого метода во время зависания в конфигураторе остановите выполнение программы (кнопка «Прервать») на исследуемом запросе и в окне «Табло» вызовите функцию экспорта:
ИмяВашегоОбщегоМодуля.ПолучитьВТ("C:\Temp_VT", ИмяПеременнойЗапроса)
После этого в указанном каталоге сформируются файлы таблиц и готовый текст запроса для консоли с инициализированными временными таблицами. Вы сможете запустить его отдельно в консоли запросов и увидеть реальный план выполнения в вашей СУБД.
Существует несколько способов борьбы с этим зависанием — от оперативных административных до системных архитектурных. Рассмотрим каждый из них.
Если рассчитывать заработную плату нужно «прямо сейчас», а система зависла, самым действенным и быстрым способом будет очистка накопившихся записей в регистре предварительных перерасчетов. Эти записи не несут непосредственных кадровых изменений, а лишь сигнализируют системе о необходимости анализа.
Выполним следующие действия:
ПредварительныеПерерасчеты программно с помощью внешней обработки или через SQL-запрос на тестовой копии БД.После удаления записей из этого регистра механизм расчета мгновенно разблокируется, нагрузка на сервер упадет до нуля, а расчет документов начисления зарплаты пройдет быстро и штатно.
Чтобы избежать этой проблемы в будущем на уровне методологии ведения учета, настоятельно рекомендуется перенастроить правила расчета в программе. По умолчанию 1С:ЗУП пытается производить доначисления и перерасчеты «на лету» прямо внутри документа начисления зарплаты. Мы можем отключить это поведение.
Разберем по шагам, как это настроить:
Начисление заработной платы система больше не будет обращаться к тяжелому регистру предварительных перерасчетов и пытаться актуализировать прошлые периоды. Основной расчет будет происходить быстро.Доначисление, перерасчет, не блокируя ежедневную работу остальных пользователей.Поскольку проблема напрямую связана со сбоем планов выполнения запросов (Parameter Sniffing) в MS SQL Server или PostgreSQL, необходимо регулярно выполнять регламентные процедуры обслуживания баз данных:
FULLSCAN. Особенно важно запускать этот процесс в ночное время перед началом активной фазы расчета заработной платы.DBCC FREEPROCCACHE;
Резюмируя: если ваша база данных 1С:ЗУП регулярно зависает при расчете зарплаты в начале месяца, в первую очередь обратите внимание на объем данных в регистре «Предварительные перерасчеты». Очистите его или переведите программу на расчет перерасчетов отдельным документом. Это решит проблему производительности раз и навсегда!