Переполнен журнал транзакций 1С — значит, файл журнала базы на MS SQL Server (.ldf) не может ни переиспользовать место, ни вырасти: кончился диск или файл упёрся в лимит. База остаётся в сети, но только на чтение: документы не проводятся, обмены и регламентные задания падают, отчёты, как правило, формируются. Ниже — диагностика по log_reuse_wait_desc, лечение, профилактика и код 1С для массовых операций. Имена условные: база trade, каталог копий E:\Backup.
Журнал транзакций для базы данных 1С на MS SQL — это файл .ldf, обычно рядом с .mdf. Когда место в нём кончается, платформа показывает текст SQL Server как есть, на языке сервера:
Ошибка СУБД: … The transaction log for database 'trade' is full due to 'LOG_BACKUP'. HRESULT=80040E14, SQLSrvr: SQLSTATE=…, state=…, Severity=11, native=9002 …Надёжный признак — native=9002, номер ошибки SQL Server. Слово после «due to» — причина, её же покажет log_reuse_wait_desc. Severity=11 — не опечатка: платформа пишет state и severity в шестнадцатеричном виде, это уровень 17.
SQL Server пишет каждое изменение сначала в журнал. Место в .ldf используется повторно только после усечения (truncation): в модели SIMPLE — на контрольной точке, в FULL — после копии журнала (BACKUP LOG). Полная копия базы журнал не усекает. Усечение освобождает место внутри файла, а размер файла уменьшает только сжатие (shrink).
Типичные сценарии в 1С:
Что мешает усечению сейчас, показывает log_reuse_wait_desc в sys.databases:
| log_reuse_wait_desc | Что значит | Что делать |
|---|---|---|
| LOG_BACKUP | FULL, журнал ждёт копии | BACKUP LOG, затем копии по расписанию |
| ACTIVE_TRANSACTION | открыта долгая транзакция | найти сеанс, дождаться или прервать |
| ACTIVE_BACKUP_OR_RESTORE | идёт копирование или восстановление | дождаться |
| CHECKPOINT | не было контрольной точки | CHECKPOINT в базе |
| REPLICATION, AVAILABILITY_REPLICA | журнал держат репликация, CDC или группа доступности | чинить их, сжатие не поможет |
| NOTHING | усечению ничего не мешает | транзакции не хватило места на рост: диск, лимит |
Скрипт ничего не меняет. Запустите его до перезапуска служб: после перезапуска виновника уже не найти.
-- Переполнен журнал транзакций: диагностика базы trade. Только чтение, ничего не меняет.
-- SSMS под администратором сервера, SQL Server 2012 и новее.
USE trade;
-- 1. Все базы сервера: размер журнала (MB) и сколько процентов занято
DBCC SQLPERF(LOGSPACE);
-- 2. Модель восстановления и что мешает усечению журнала
SELECT name, recovery_model_desc, log_reuse_wait_desc
FROM sys.databases
WHERE database_id > 4
ORDER BY name;
-- 3. Файл журнала базы: размер, заполнение, приращение, лимит и свободное место на его диске
SELECT f.name AS log_name, f.physical_name, f.size / 128 AS log_mb,
CAST(u.used_log_space_in_percent AS decimal(5, 1)) AS used_pct,
IIF(f.is_percent_growth = 1, CONCAT(f.growth, N' %'), CONCAT(f.growth / 128, N' MB')) AS growth,
IIF(f.max_size IN (-1, 268435456), N'нет', CONCAT(f.max_size / 128, N' MB')) AS size_limit,
v.available_bytes / 1048576 AS disk_free_mb
FROM sys.database_files AS f
CROSS JOIN sys.dm_db_log_space_usage AS u
CROSS APPLY sys.dm_os_volume_stats(DB_ID(), f.file_id) AS v
WHERE f.type_desc = N'LOG';
-- 4. Копии базы за 30 дней: D - полная, I - разностная, L - журнал
SELECT type, MAX(backup_finish_date) AS last_backup, COUNT(*) AS backups
FROM msdb.dbo.backupset
WHERE database_name = DB_NAME()
AND backup_finish_date > DATEADD(DAY, -30, GETDATE())
GROUP BY type;
-- 5. Открытые транзакции в базе: кто держит журнал и сколько в него записал
SELECT s.session_id, s.program_name, s.host_name, s.login_name, s.status,
t.database_transaction_begin_time AS tran_begin,
t.database_transaction_log_bytes_used / 1048576 AS log_mb,
s.last_request_end_time
FROM sys.dm_tran_database_transactions AS t
JOIN sys.dm_tran_session_transactions AS st ON st.transaction_id = t.transaction_id
JOIN sys.dm_exec_sessions AS s ON s.session_id = st.session_id
WHERE t.database_id = DB_ID()
ORDER BY t.database_transaction_begin_time;
-- 6. Самая старая активная транзакция базы: SPID и время начала
DBCC OPENTRAN;
При LOG_BACKUP основное лечение — копия журнала. Её пишут на другой диск или в сетевую папку (учётной записи службы SQL Server нужно право записи), поэтому полный диск с .ldf не мешает. Пользователей выгонять не нужно: копия и сжатие журнала идут при работающей 1С.
-- Освободить и сжать журнал базы trade в модели FULL, не разрывая цепочку копий.
-- E:\Backup - другой диск или сетевая папка с правом записи для учетной записи службы SQL Server.
USE master;
DECLARE @file nvarchar(260) = N'E:\Backup\trade_log_' + FORMAT(SYSDATETIME(), N'yyyyMMdd_HHmmss') + N'.trn';
-- 1. Копия журнала: после нее место внутри .ldf снова можно использовать
BACKUP LOG trade TO DISK = @file WITH CHECKSUM, STATS = 10;
-- 2. Ждем NOTHING или CHECKPOINT. Снова LOG_BACKUP - еще одна копия, ACTIVE_TRANSACTION - блок 5 диагностики
SELECT name, log_reuse_wait_desc FROM sys.databases WHERE name = N'trade';
-- 3. Логическое имя файла журнала: SHRINKFILE принимает его, а не имя базы
USE trade;
SELECT name, physical_name, size / 128 AS size_mb FROM sys.database_files WHERE type_desc = N'LOG';
-- 4. Сжать до рабочего размера в мегабайтах: здесь 8 ГБ, подставьте свой
DBCC SHRINKFILE (N'trade_log', 8192);
-- 5. Файл не уменьшился - активная часть журнала в конце файла. Еще одна копия и повтор:
-- BACKUP LOG trade TO DISK = N'E:\Backup\trade_log_2.trn' WITH CHECKSUM;
-- DBCC SHRINKFILE (N'trade_log', 8192);
-- 6. Дальше журнал растет фиксированным шагом, а не процентами
ALTER DATABASE trade MODIFY FILE (NAME = N'trade_log', FILEGROWTH = 512MB);
Если журнал ни разу не копировался, по справке Microsoft нужны две копии подряд. Сжимайте не «до нуля», а до рабочего размера — сколько журнал занимает между копиями в самый тяжёлый день, с запасом: иначе файл начнёт расти обратно, а приращение журнала обычно идёт с паузой на заполнение нового участка нулями.
Популярный рецепт — перевести базу в SIMPLE, сжать журнал, вернуть FULL; такой же пример есть в справке Microsoft по DBCC SHRINKFILE. Он оправдан, когда копию журнала положить некуда (она почти равна занятой части .ldf), а полную копию базы — есть куда. Цена — разрыв цепочки копий.
-- Аварийный вариант: копию журнала сделать некуда, полную копию базы - есть куда.
-- Цепочка копий журнала рвется: восстановить базу на момент между последней копией
-- журнала и полной копией из шага 4 будет нельзя. Есть место - сначала BACKUP LOG.
USE master;
ALTER DATABASE trade SET RECOVERY SIMPLE;
-- 1. В SIMPLE журнал усекается на контрольной точке
USE trade;
CHECKPOINT;
-- 2. Имя файла журнала, а не базы. Размер - рабочий, не 0
SELECT name, size / 128 AS size_mb FROM sys.database_files WHERE type_desc = N'LOG';
DBCC SHRINKFILE (N'trade_log', 8192);
-- 3. Хватает ночной полной копии - остановитесь здесь: база остается в SIMPLE,
-- задание копий журнала отключите. Нужна точка восстановления на любой момент - FULL
USE master;
ALTER DATABASE trade SET RECOVERY FULL;
-- 4. Сразу полная копия: без нее база в FULL работает как SIMPLE, а BACKUP LOG
-- завершается ошибкой. С этой копии начинается новая цепочка
DECLARE @file nvarchar(260) = N'E:\Backup\trade_full_' + FORMAT(SYSDATETIME(), N'yyyyMMdd_HHmmss') + N'.bak';
BACKUP DATABASE trade TO DISK = @file WITH CHECKSUM, STATS = 10;
Что исправлено по сравнению с популярной инструкцией:
DBCC SHRINKFILE ([имя_базы], 50) сжимает .mdf: долго, с фрагментацией индексов, а журнал не трогает.В SSMS то же мышью: свойства базы → «Параметры» → «Модель восстановления»: «Простая»; затем «Задачи → Сжать → Файлы», «Тип файла» — «Журнал», «Сжать файл до» — рабочий размер в мегабайтах; потом модель обратно на «Полная» и полная резервная копия.
При ACTIVE_TRANSACTION блок 5 диагностики покажет сеанс сервера 1С, начало транзакции и объём, записанный в журнал. Закрытию месяца или загрузке, запущенным осознанно, лучше дать закончиться, добавив журналу места. Зависший сеанс завершите в консоли кластера 1С — у сеанса с открытой транзакцией заполнены колонки «Соединение с СУБД» и «Захвачено СУБД». KILL в SSMS — крайняя мера.
-- Аварийные команды для базы trade: выполняйте по одной, а не всем скриптом.
-- Журналу когда-то ограничили размер (size_limit в блоке 3 диагностики): снять лимит
ALTER DATABASE trade MODIFY FILE (NAME = N'trade_log', MAXSIZE = UNLIMITED);
-- Нужная транзакция (закрытие месяца) еще идет, а диск с .ldf кончился:
-- временный второй файл журнала на другом диске
ALTER DATABASE trade ADD LOG FILE
(NAME = N'trade_log_tmp', FILENAME = N'F:\SQLTemp\trade_log_tmp.ldf', SIZE = 8GB, FILEGROWTH = 1GB);
-- Зависшую транзакцию решили прервать: 87 - session_id из блока 5 диагностики
-- KILL 87;
-- KILL 87 WITH STATUSONLY; -- ход отката: процент и оценка оставшегося времени
-- После разбора, когда журнал снова усекается, временный файл убирают.
-- Ответ, что файл не пуст, - еще одна копия журнала и повтор
-- ALTER DATABASE trade REMOVE FILE trade_log_tmp;
Откат тоже пишет в журнал и может идти столько же, сколько сама транзакция. Перезапуск SQL Server его не ускорит: при старте база откатит ту же транзакцию и всё это время может быть недоступна.
-- Профилактика для базы trade в модели FULL: копия журнала каждые 30 минут
-- и внеочередная, если журнал заполнен больше чем на 70%. Нужен SQL Server Agent.
USE msdb;
-- 1. Задание с одним шагом T-SQL: файл копии с датой и временем в имени
EXEC dbo.sp_add_job @job_name = N'trade - копия журнала',
@description = N'BACKUP LOG trade в E:\Backup\trade_log_*.trn';
EXEC dbo.sp_add_jobstep @job_name = N'trade - копия журнала', @step_name = N'BACKUP LOG',
@subsystem = N'TSQL', @database_name = N'master',
@command = N'DECLARE @file nvarchar(260) = N''E:\Backup\trade_log_''
+ FORMAT(SYSDATETIME(), N''yyyyMMdd_HHmmss'') + N''.trn'';
BACKUP LOG trade TO DISK = @file WITH CHECKSUM;',
@retry_attempts = 2, @retry_interval = 5; -- при сбое повтор через 5 минут
-- 2. Расписание: ежедневно, каждые 30 минут круглые сутки
EXEC dbo.sp_add_schedule @schedule_name = N'Каждые 30 минут',
@freq_type = 4, @freq_interval = 1,
@freq_subday_type = 4, @freq_subday_interval = 30;
EXEC dbo.sp_attach_schedule @job_name = N'trade - копия журнала', @schedule_name = N'Каждые 30 минут';
EXEC dbo.sp_add_jobserver @job_name = N'trade - копия журнала', @server_name = N'(local)';
-- 3. Предупреждение по счетчику: журнал trade занят больше чем на 70% - то же задание вне расписания.
-- У именованного экземпляра объект счетчика называется MSSQL$ИМЯ:Databases
EXEC dbo.sp_add_alert @name = N'trade - журнал больше 70%',
@performance_condition = N'SQLServer:Databases|Percent Log Used|trade|>|70',
@delay_between_responses = 900, -- секунды: не чаще раза в 15 минут
@job_name = N'trade - копия журнала';
В Express агента нет: копию запускают через sqlcmd из Планировщика заданий Windows или держат базу в SIMPLE. Файлы .trn храните вместе с полной копией, от которой идёт цепочка; старые удаляет задача очистки (Maintenance Cleanup Task) плана обслуживания.
Со стороны кода журнал раздувает обработка, которая заворачивает тысячи записей в одну НачатьТранзакцию(): пока транзакция открыта, журнал не усекается. Платформа и так записывает каждый объект в своей транзакции, общая обычно не нужна — так, по одному, проводит документы и функция БСП ОбщегоНазначения.ПровестиДокументы. Функция ниже добавляет окно времени и остановку на ошибке 9002. Шаблон явной транзакции — в статье о транзакциях в 1С.
// Перепроведение документов одного вида за период без общей транзакции.
// Каждый документ проводится в своей короткой транзакции, поэтому SQL Server может
// усекать журнал по ходу работы: в SIMPLE - на контрольной точке, в FULL - при копии журнала.
// Запуск на сервере (фоновое или регламентное задание), например:
// ПерепровестиБезОбщейТранзакции("РеализацияТоваровУслуг", '20260901', '20260930235959', '20261008063000');
Функция ПерепровестиБезОбщейТранзакции(ИмяДокумента, НачалоПериода, КонецПериода, ОстановитьВ = Неопределено) Экспорт
// Внутри внешней транзакции вся обработка снова стала бы одной длинной транзакцией
Если ТранзакцияАктивна() Тогда
ВызватьИсключение "Перепроведение нельзя запускать внутри транзакции";
КонецЕсли;
МетаданныеДокумента = Метаданные.Документы.Найти(ИмяДокумента);
Если МетаданныеДокумента = Неопределено Тогда
ВызватьИсключение СтрШаблон("В конфигурации нет документа ""%1""", ИмяДокумента);
КонецЕсли;
Если МетаданныеДокумента.Проведение <> Метаданные.СвойстваОбъектов.Проведение.Разрешить Тогда
ВызватьИсключение СтрШаблон("Документ ""%1"" не проводится", ИмяДокумента);
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст = СтрЗаменить(
"ВЫБРАТЬ
| Док.Ссылка КАК Ссылка,
| Док.Дата КАК Дата
|ИЗ
| Документ.ИмяДокумента КАК Док
|ГДЕ
| Док.Проведен
| И Док.Дата МЕЖДУ &НачалоПериода И &КонецПериода
|
|УПОРЯДОЧИТЬ ПО
| Док.МоментВремени", "ИмяДокумента", ИмяДокумента);
Запрос.УстановитьПараметр("НачалоПериода", НачалоПериода);
Запрос.УстановитьПараметр("КонецПериода", КонецПериода);
СписокДокументов = Запрос.Выполнить().Выгрузить();
Событие = "Перепроведение документов";
Проведено = 0;
Ошибок = 0;
ПродолжитьС = Неопределено;
Для Каждого СтрокаДокумента Из СписокДокументов Цикл
// Ночное окно закончилось: следующий запуск начните с даты этого документа
Если ОстановитьВ <> Неопределено И ТекущаяДатаСеанса() >= ОстановитьВ Тогда
ПродолжитьС = СтрокаДокумента.Дата;
Прервать;
КонецЕсли;
ДокументОбъект = СтрокаДокумента.Ссылка.ПолучитьОбъект();
Если ДокументОбъект = Неопределено Или Не ДокументОбъект.Проведен Тогда
Продолжить; // удален или распроведен, пока шла обработка
КонецЕсли;
Попытка
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение, РежимПроведенияДокумента.Неоперативный);
Проведено = Проведено + 1;
Исключение
ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
Если ЭтоПереполнениеЖурналаТранзакций(ТекстОшибки) Тогда
// Дальше каждая запись упадет так же: останавливаемся и зовем администратора
ЗаписьЖурналаРегистрации(Событие, УровеньЖурналаРегистрации.Ошибка,
МетаданныеДокумента, СтрокаДокумента.Ссылка, ТекстОшибки);
ВызватьИсключение СтрШаблон("Журнал транзакций СУБД переполнен. Проведено %1, ошибок %2, продолжить с %3",
Проведено, Ошибок, Формат(СтрокаДокумента.Дата, "ДЛФ=DT"));
КонецЕсли;
Ошибок = Ошибок + 1;
ЗаписьЖурналаРегистрации(Событие, УровеньЖурналаРегистрации.Предупреждение,
МетаданныеДокумента, СтрокаДокумента.Ссылка, ТекстОшибки);
КонецПопытки;
КонецЦикла;
Итог = СтрШаблон("%1: проведено %2, ошибок %3, всего в периоде %4",
ИмяДокумента, Проведено, Ошибок, СписокДокументов.Количество());
Если ПродолжитьС <> Неопределено Тогда
Итог = Итог + СтрШаблон("; окно закончилось, продолжить с %1", Формат(ПродолжитьС, "ДЛФ=DT"));
КонецЕсли;
ЗаписьЖурналаРегистрации(Событие, УровеньЖурналаРегистрации.Информация, МетаданныеДокумента, , Итог);
Возврат Итог;
КонецФункции
// Истина, если ошибка СУБД - переполненный журнал транзакций MS SQL (ошибка 9002).
// Текст сообщения зависит от языка SQL Server, а номер в строке ошибки 1С - нет.
Функция ЭтоПереполнениеЖурналаТранзакций(ТекстОшибки) Экспорт
Текст = НРег(ТекстОшибки);
Возврат СтрНайти(Текст, "native=9002") > 0
Или СтрНайти(Текст, "transaction log for database") > 0
Или СтрНайти(Текст, "журнал транзакций для базы данных") > 0;
КонецФункции
В FULL разбиение не уменьшает объём журнала, но позволяет копиям по расписанию освобождать место по ходу работы; в SIMPLE его освобождает контрольная точка.
log_reuse_wait_desc — почему журнал нельзя усечь сейчас; от этого зависит способ лечения.used_log_space_in_percent — заполнение журнала: 99% при LOG_BACKUP — нужна копия; несколько процентов при огромном файле — осталось сжать.L — копий журнала не было, причина найдена.database_transaction_log_bytes_used — сколько транзакция записала в журнал; самая старая строка с большим объёмом — виновник.DBCC SHRINKFILE (N'trade_log', 8192) — логическое имя и размер в мегабайтах; журнал сжимается только до границы виртуального журнала (VLF).SQLServer:Databases|Percent Log Used|trade|>|70 — объект, счётчик, экземпляр счётчика (имя базы), сравнение и порог.ТранзакцияАктивна() — защита от вызова внутри внешней транзакции; ПродолжитьС — дата документа, с которого начать следующий запуск.native=9002 — номер ошибки в тексте исключения 1С, он не зависит от языка сервера.Нет. Журнал — часть базы, без него SQL Server не гарантирует целостность, и базу с потерянным журналом обычно восстанавливают из копии. Место освобождают копией журнала и SHRINKFILE.
SIMPLE, если допустимо потерять работу с момента последней полной копии, обычно ночной. FULL — если нужна точка восстановления на любой момент, и тогда копии журнала по расписанию обязательны.
Копия освобождает место внутри файла, размер меняет только SHRINKFILE. Не помогает и он — журнал держит транзакция или репликация (покажет log_reuse_wait_desc) либо активная часть в конце файла.
Универсальной доли от размера базы нет. Посмотрите used_log_space_in_percent перед плановой копией в самый тяжёлый день и задайте размер с запасом. Журнал, который регулярно дорастает до одного размера, сжимать не нужно.
В файловой базе журнала СУБД нет, и этой ошибки тоже. В PostgreSQL рост каталога pg_wal — отдельная история с другими причинами (архивация WAL, слоты репликации), команды MS SQL там не годятся.
Если журнал переполняется снова, хотя копии настроены, причина обычно в коде: долгие транзакции в обработках, обменах и регламентных заданиях. Опишите ситуацию в услуге «1С тормозит» или разместите задачу — оценка бесплатная, ответ в течение рабочего дня.
Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.