Когда вы работаете с большими объемами данных в системе 1С:Предприятие, особенно при выполнении операций массового удаления записей из регистров сведений, вы можете столкнуться с неожиданными ошибками. Одна из таких распространенных и критически важных проблем — это переполнение журнала транзакций SQL Server, которое проявляется сообщением вида "Журнал транзакций для базы данных переполнен. Причина: ACTIVE_TRANSACTION". Эта ошибка не только останавливает выполнение операции, но и может указывать на проблемы с конфигурацией или обслуживанием вашей базы данных SQL Server. В этой статье мы подробно разберем, почему возникает эта ошибка, и какие шаги необходимо предпринять для ее эффективного устранения, основываясь на практическом опыте.
Рассмотрим типовую ситуацию. Вы пытаетесь очистить регистр сведений, например, РегистрыСведений.ВерсииОбъектов, который со временем накапливает огромное количество записей. Для этого вы используете стандартный программный подход 1С:
НаборЗаписей = РегистрыСведений.ВерсииОбъектов.СоздатьНаборЗаписей();
НаборЗаписей.Записать();
Этот код предназначен для полной очистки регистра сведений, если на набор записей не установлен отбор. Однако при наличии миллионов записей выполнение такой операции может затянуться на длительное время, потребляя значительные ресурсы. В результате 1С может "повиснуть" и в конечном итоге завершиться с ошибкой:
Невосстановимая ошибка
Ошибка при выполнении запроса POST к ресурсу /e1cib/logForm:
по причине:
Ошибка СУБД:
Microsoft SQL Server Native Client 11.0: Журнал транзакций для базы данных "ERPTest" переполнен. Причина: "ACTIVE_TRANSACTION".
HRESULT=80040E14, SQLSrvr: SQLSTATE=42000, state=4, Severity=11, native=9002, line=1
Ключевая часть этого сообщения — "Журнал транзакций для базы данных "ERPTest" переполнен. Причина: "ACTIVE_TRANSACTION"". Это указывает на то, что проблема лежит на стороне SQL Server, а не непосредственно в коде 1С, хотя код 1С инициировал транзакцию, которая привела к переполнению. Давайте выясним, что это означает.
Журнал транзакций (Transaction Log) в SQL Server — это критически важный компонент, который записывает все изменения, происходящие в базе данных. Он используется для обеспечения целостности данных, восстановления базы данных после сбоев и реализации резервного копирования. Когда SQL Server сообщает о переполнении журнала транзакций с причиной "ACTIVE_TRANSACTION", это означает, что журнал не может повторно использовать свободное место, потому что одна или несколько активных транзакций удерживают это место.
Рассмотрим подробнее основные факторы, которые приводят к такой ситуации:
FULL (полная) восстановления журнал транзакций не очищается автоматически. Он будет усечен (освобождено неактивное пространство) только после выполнения резервного копирования журнала транзакций. Если бэкапы журнала не делаются регулярно, особенно перед крупными операциями, журнал будет постоянно расти.SIMPLE (простая) восстановления журнал транзакций усекается автоматически, когда это возможно, без необходимости выполнять резервное копирование журнала. Это может предотвратить его чрезмерный рост, но при этом теряется возможность восстановления базы данных до любой точки во времени (только до последнего полного или разностного бэкапа).Autogrowth). Если максимальный размер файла журнала слишком мал или прирост происходит слишком медленно/неэффективно, журнал может быстро достичь своего лимита.FULL, если не выполняются регулярные бэкапы журнала, SQL Server не может пометить старые записи журнала как неактивные и освободить место, что приводит к непрерывному росту файла.Основываясь на опыте участников форума, ключевым шагом в решении этой проблемы является правильная настройка и управление журналом транзакций SQL Server. Давайте разберем по шагам, что необходимо сделать.
Прежде всего, мы должны проверить, какая модель восстановления установлена для вашей базы данных. Если она установлена в FULL, и вы не выполняете регулярные резервные копирования журнала, это может быть основной причиной. Однако, как показал случай в обсуждаемой теме, даже при установленной модели SIMPLE проблема может возникнуть, если есть жесткое ограничение на рост лога.
Как проверить и изменить модель восстановления:
Откроем SQL Server Management Studio (SSMS), подключимся к вашему серверу SQL Server. В обозревателе объектов разверните узел "Базы данных", найдите вашу базу данных (например, ERPTest), кликните правой кнопкой мыши и выберите "Свойства". В окне "Свойства базы данных" перейдите на страницу "Параметры". В разделе "Модель восстановления" вы увидите текущую настройку.
FULL: Убедимся, что вы выполняете регулярное резервное копирование журнала транзакций. Для немедленной очистки лога (если это безопасно для ваших требований к восстановлению), можно выполнить команду бэкапа журнала:
BACKUP LOG [Имя_Вашей_Базы_Данных] TO DISK = 'NUL:'
Эта команда выполнит резервное копирование журнала в "пустое" устройство, усекая его и освобождая место. ВАЖНО: Использование 'NUL:' не создает реальный файл резервной копии, поэтому этот шаг должен быть выполнен только если вы уверены в наличии других актуальных бэкапов.
SIMPLE: В этом случае журнал должен усекаться автоматически. Если проблема все равно возникает, это указывает на другие факторы, такие как длительность транзакции или ограничения на рост файла, как это было в исходной теме.После усечения журнала вы можете физически уменьшить размер файла журнала, если он разросся слишком сильно, используя команду DBCC SHRINKFILE. Откроем новое окно запроса в SSMS и выполним:
USE [Имя_Вашей_Базы_Данных];
GO
DBCC SHRINKFILE (N'Имя_Лог_Файла', 1024); -- Укажите желаемый размер в МБ
GO
Для определения имени логического файла журнала (N'Имя_Лог_Файла') мы можем посмотреть его в свойствах базы данных на странице "Файлы".
Как выяснилось в обсуждаемой теме, даже при установленной простой модели восстановления и, казалось бы, достаточном резерве, наличие ограничения на рост файла журнала может стать причиной ошибки. Это означает, что если журнал достигает своего максимально заданного размера (даже если этот размер кажется большим), он не может дальше расти, и любая активная транзакция, требующая дополнительного места, приведет к переполнению.
Как проверить и изменить настройки Autogrowth:
Снова откроем SQL Server Management Studio (SSMS). В свойствах вашей базы данных (правый клик -> "Свойства") перейдите на страницу "Файлы". Найдите в списке файл типа "Журнал" (Log) – обычно его логическое имя заканчивается на "_log".
Обратим внимание на следующие настройки:
После изменения этих настроек и применения их к базе данных, повторим попытку массового удаления записей 1С. В большинстве случаев это позволит транзакции успешно завершиться.
Для повышения производительности и надежности рассмотрим возможность размещения файлов данных (.mdf) и файлов журнала транзакций (.ldf) на разных физических дисках, предпочтительно SSD. Это снизит конкуренцию за дисковые операции ввода/вывода между чтением/записью данных и записью в журнал.
Помимо решения проблем на уровне SQL Server, рассмотрим другие подходы к удалению большого количества записей в 1С, которые могут быть более эффективными или управляемыми в определенных ситуациях. Классический метод НаборЗаписей.Записать(); без отбора, как мы уже видели, очищает регистр полностью и может быть проблематичен для очень больших объемов.
Если необходимо удалить не все записи, а только их часть, или если требуется разбить одну большую операцию на несколько меньших транзакций, используем отбор для НабораЗаписей. Это позволит удалять данные порциями, что значительно снизит нагрузку на журнал транзакций SQL Server и уменьшит вероятность ошибки "ACTIVE_TRANSACTION". Для такого действия подойдёт универсальная обработка для удаления записей регистров сведений по фильтрам.
Как это реализовать:
Мы можем установить отбор по одному из измерений регистра (например, по периоду или по какому-либо ID), а затем в цикле удалять записи по частям, завершая каждую порцию отдельной транзакцией.
// Пример порционного удаления по некоторому идентификатору или диапазону
// Предполагаем, что у регистра есть измерение "Объект" типа СсылкаНаОбъект
// и мы хотим удалить все версии для некоторого типа объектов.
// Определим параметры для порционного удаления.
// Например, будем удалять по 1000 записей за раз.
КоличествоЗаписейВПорции = 1000;
УдаленоВсего = 0;
Пока Истина Цикл
// Используем запрос для выбора ключей записей, которые нужно удалить
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ &КоличествоЗаписейВПорции
| ВерсииОбъектов.Объект,
| ВерсииОбъектов.НомерВерсии,
| ВерсииОбъектов.Период
|ИЗ
| РегистрСведений.ВерсииОбъектов КАК ВерсииОбъектов
|ГДЕ
| ВерсииОбъектов.Объект ССЫЛКА Справочник.Номенклатура
| // Дополнительные условия отбора, если нужно удалить не все версии Номенклатуры
|
|УПОРЯДОЧИТЬ ПО
| ВерсииОбъектов.Объект, ВерсииОбъектов.НомерВерсии"; // Важно для стабильного получения порций
Запрос.УстановитьПараметр("КоличествоЗаписейВПорции", КоличествоЗаписейВПорции);
Выборка = Запрос.Выполнить().Выбрать();
Если Не Выборка.Следующий() Тогда
// Нет больше записей для удаления
Прервать;
КонецЕсли;
НаборЗаписейДляУдаления = РегистрыСведений.ВерсииОбъектов.СоздатьНаборЗаписей();
Пока Выборка.Следующий() Цикл
// Для удаления конкретной записи через набор,
// мы должны установить ее значения в качестве отбора
// и затем записать пустой набор.
// Но более простой и эффективный способ для порционного удаления:
// получить менеджер записи и удалить каждую запись отдельно.
// Или, что предпочтительнее для порционного подхода, использовать запрос.
// Более эффективное порционное удаление:
// Вместо создания множества МенеджеровЗаписи,
// мы можем использовать метод Удалить() с отбором.
// Для этого нужно установить отбор по комбинации измерений для каждой записи,
// но это все равно будет медленно.
// Самый эффективный вариант для порционного удаления в 1С
// без использования прямого SQL - это Удалить(Отбор).
// Но он не позволяет напрямую удалить порцию по произвольному запросу.
// Если мы хотим удалить именно по порциям, то лучше создать временную таблицу
// с ключами для удаления и затем итерироваться по ней.
// Однако, для простоты и в контексте задачи "удалить все записи"
// или большую их часть, рассмотрим более прямолинейный подход.
// Для порционного удаления с отбором, как было сказано в исходной теме
// (если не все записи, а часть), мы можем итерироваться по какому-либо измерению.
// Например, если у нас есть измерение с уникальными значениями,
// мы можем удалять по диапазону этих значений.
// В случае ВерсииОбъектов обычно есть объект, номер версии, период.
// Для удаления порций можно брать диапазон объектов или номеров версий:
// РегистрыСведений.ВерсииОбъектов.Удалить(НачалоДиапазонаКлючей, КонецДиапазонаКлючей); // Этот метод не существует напрямую
// Мы используем НаборЗаписей с отбором.
КонецЦикла;
// В данном случае, если нам нужно удалить *все* записи регистра сведений,
// и мы не можем использовать прямой SQL, а платформа не справляется с НаборЗаписей.Записать()
// без отбора, то порционное удаление через УДАЛИТЬ() с отбором
// может быть самым адекватным способом.
// Пример порционного удаления через .Удалить(Отбор)
// Этот метод удаляет записи, соответствующие отбору.
// Если нам нужно удалить все записи из регистра,
// но по частям, мы можем итерироваться, например, по уникальным значениям
// одного из измерений.
НачатьТранзакцию();
Попытка
КоличествоУдаленныхВТранзакции = 0;
Выборка.Сбросить(); // Сбросим выборку для повторного прохода
Пока Выборка.Следующий() Цикл
// Создаем временный набор записей для одной записи или небольшого отбора
НаборЗаписейПорции = РегистрыСведений.ВерсииОбъектов.СоздатьНаборЗаписей();
НаборЗаписейПорции.Отбор.Объект.Установить(Выборка.Объект);
НаборЗаписейПорции.Отбор.НомерВерсии.Установить(Выборка.НомерВерсии);
НаборЗаписейПорции.Отбор.Период.Установить(Выборка.Период); // Если период есть и входит в состав ключа
// Удаляем записи, соответствующие отбору.
// Если отбор полный по всем измерениям, удаляется ровно одна запись.
НаборЗаписейПорции.Записать(); // Записываем пустой набор для удаления по отбору
КоличествоУдаленныхВТранзакции = КоличествоУдаленныхВТранзакции + 1;
Если КоличествоУдаленныхВТранзакции % КоличествоЗаписейВПорции = 0 Тогда
ЗафиксироватьТранзакцию();
Сообщить("Удалено " + КоличествоУдаленныхВТранзакции + " записей. Обновлено всего: " + (УдаленоВсего + КоличествоУдаленныхВТранзакции));
НачатьТранзакцию();
КонецЕсли;
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
Сообщить("Ошибка при удалении порции: " + ОписаниеОшибки());
Прервать;
КонецПопытки;
УдаленоВсего = УдаленоВсего + КоличествоУдаленныхВТранзакции;
Сообщить("Удалена очередная порция записей. Всего удалено: " + УдаленоВсего);
// Если мы удаляем по порциям, то после удаления одной порции,
// следующая итерация цикла "Пока Истина" будет запрашивать следующую порцию.
// Если же запрос не нашел новых записей, цикл прервется.
// Добавим небольшую задержку, чтобы снизить нагрузку
// Запускаем ФоновоеЗадание или ДлительныеОперации для контроля
КонецЦикла;
Этот подход требует более сложной логики для определения "порций" данных и управления транзакциями, но он значительно надежнее для очень больших объемов, так как разбивает одну гигантскую транзакцию на множество мелких.
МенеджераЗаписи (для единичных удалений или небольших порций):
Для удаления одной или нескольких записей, соответствующих определенным значениям измерений, можно использовать МенеджерЗаписи. Однако, для очень больших объемов данных платформа все равно может конвертировать операции МенеджераЗаписи в более крупные транзакции или пакеты, что делает его неоптимальным для массовой очистки, если нет явной логики порционного коммита.
// Пример удаления одной записи через МенеджерЗаписи
МенеджерЗаписи = РегистрыСведений.ВерсииОбъектов.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Объект = МояСсылкаНаОбъект; // Измерение Объект
МенеджерЗаписи.НомерВерсии = 123; // Измерение НомерВерсии
// Если Период является частью ключа, его тоже нужно установить
МенеджерЗаписи.Период = '20230101';
МенеджерЗаписи.Прочитать(); // Считываем запись, чтобы убедиться в ее существовании
Если МенеджерЗаписи.Прочитана Тогда
МенеджерЗаписи.Удалить();
КонецЕсли;
Этот метод лучше подходит для точечного удаления, а не для полной очистки регистра.
Существуют универсальные внешние обработки для 1С, предназначенные для очистки регистров сведений. Они могут предлагать более гибкие способы отбора, фильтрации и, возможно, оптимизированные алгоритмы для работы с большими объемами данных. Некоторые из них могут выполнять удаление в монопольном режиме или использовать встроенные механизмы, которые позволяют эффективно управлять транзакциями. Для этой задачи можно использовать обработку для полной очистки независимых регистров сведений.
При выполнении операций массового удаления, если это возможно, запускайте 1С в монопольном (эксклюзивном) режиме. Это гарантирует, что никакие другие пользователи или фоновые процессы не будут блокировать данные, что может значительно ускорить процесс, так как система не отвлекается на управление конкурентным доступом и блокировками.
Для самых больших объемов данных, когда программные методы 1С оказываются неэффективными или слишком медленными даже после оптимизации SQL Server, некоторые специалисты прибегают к прямому удалению через SQL-запросы. Этот метод является высокорискованным и требует глубокого понимания структуры базы данных 1С и принципов работы SQL Server.
Мы настоятельно рекомендуем использовать этот метод только в крайних случаях и только после тщательной подготовки!
DELETE FROM WHERE:
Этот метод позволяет удалить записи напрямую из таблицы базы данных SQL Server. Он может быть значительно быстрее, чем программные методы 1С, поскольку обходит часть служебных механизмов платформы.
Особые меры предосторожности:
DELETE может вызвать блокировки и длительное выполнение, если удаляется очень большой объем данных одной транзакцией.Как найти имя таблицы:
Чтобы использовать прямой SQL-запрос, мы должны знать фактическое имя таблицы регистра сведений в SQL Server. В 1С это имя обычно имеет вид _1SRGxxxx или _1Sxxxx. Мы можем найти его, например, через функцию 1С ПолучитьСтруктуруХраненияБазыДанных() в режиме отладки или через метаданные конфигурации в конфигураторе, просматривая структуру хранения регистра.
Предположим, что для регистра РегистрыСведений.ВерсииОбъектов соответствующая таблица в SQL Server называется _1SRG12345.
Пример SQL-запроса для удаления всех записей (используйте только после полной резервной копии!):
USE [Имя_Вашей_Базы_Данных];
GO
DELETE FROM _1SRG12345; -- Удаляет все записи из таблицы
GO
Если необходимо удалить только часть записей, мы должны использовать условие WHERE:
USE [Имя_Вашей_Базы_Данных];
GO
DELETE FROM _1SRG12345 WHERE _Fld12345 = 123; -- Удаляет записи, где значение поля _Fld12345 равно 123
GO
Поля в SQL Server для регистров сведений в 1С имеют специфические названия (например, _Fld12345 для измерений, _Fld12346 для ресурсов). Необходимо предварительно изучить структуру таблицы. Найти соответствие можно, например, через запрос к системным представлениям SQL Server или используя специальные обработки для анализа структуры базы данных 1С.
Для управления размером транзакций и снижения нагрузки на журнал, даже при прямом SQL-удалении можно использовать пакетный подход. Это можно реализовать с помощью цикла или скрипта, который удаляет данные порциями, коммитя каждую порцию.
USE [Имя_Вашей_Базы_Данных];
GO
SET NOCOUNT ON;
DECLARE @RowsAffected INT;
SET @RowsAffected = 1;
WHILE (@RowsAffected > 0)
BEGIN
DELETE TOP (100000) FROM _1SRG12345
WHERE _Fld12345 <= GETDATE(); -- Пример условия для порционного удаления, где _Fld12345 это поле даты
SET @RowsAffected = @@ROWCOUNT;
-- Можно добавить задержку для снижения нагрузки
-- WAITFOR DELAY '00:00:01';
PRINT CAST(@RowsAffected AS VARCHAR(10)) + ' rows deleted.';
END;
GO
Этот скрипт будет удалять по 100 000 записей за раз, пока не удалит все, соответствующие условию, или пока не останется записей для удаления. Это помогает держать размер транзакции под контролем, предотвращая переполнение журнала.
Прежде чем приступать к масштабным операциям с базой данных, таким как массовое удаление записей, всегда следуем этим важным рекомендациям, чтобы обеспечить стабильность и целостность вашей информационной системы.
Это самый важный шаг. Мы должны всегда делать полную и актуальную резервную копию базы данных непосредственно перед выполнением любых операций по массовому изменению или удалению данных. Это позволит нам восстановить систему до рабочего состояния в случае непредвиденных ошибок или повреждения данных.
После крупных операций по удалению данных, особенно если использовались прямые SQL-запросы, мы настоятельно рекомендуем выполнить "Тестирование и исправление" в конфигураторе 1С. Для автоматического проведения проверки и регламентных процедур в нескольких информационных базах можно задействовать инструмент автоматического тестирования и обслуживания баз 1С. Эта процедура помогает:
Запустим конфигуратор, перейдем в меню "Администрирование" -> "Тестирование и исправление...". Выберем необходимые опции, такие как "Реиндексация", "Сжатие таблиц ИБ", "Пересчет итогов" и "Проверка логической целостности".
Регулярное обслуживание базы данных SQL Server является ключом к поддержанию высокой производительности. Это включает в себя:
ALTER INDEX REBUILD) для устранения фрагментации и повышения эффективности поиска данных.UPDATE STATISTICS) помогает оптимизатору запросов SQL Server строить более эффективные планы выполнения запросов.Эти процедуры можно автоматизировать с помощью планов обслуживания SQL Server.
Проблема переполнения журнала транзакций SQL Server при массовом удалении записей в 1С — это комплексная задача, требующая внимания как к настройкам SQL Server, так и к методам работы с данными в 1С. Как показал практический опыт, в большинстве случаев адекватная настройка параметров роста файла журнала транзакций является наиболее эффективным решением. Всегда подходим к таким операциям с осторожностью, обеспечивая надежное резервное копирование и последующую проверку целостности данных.