Quick Answer
Transaction log growth occurs when SQL Server cannot truncate committed transactions from the log file, causing continuous expansion. Primary causes include full recovery model without log backups, long-running transactions, or replication latency. This is always concerning as it leads to disk space exhaustion and performance degradation.
Root Cause Analysis
The transaction log operates as a circular buffer where SQL Server writes new transactions at the logical end while attempting to truncate committed transactions from the beginning. Log truncation depends on the log_reuse_wait_desc value in sys.databases, which indicates what prevents the log from being reused.
In FULL recovery model, log truncation requires transaction log backups. Without them, the log grows indefinitely because SQL Server must retain all log records for point-in-time recovery. SIMPLE recovery model truncates the log at checkpoint operations, but active transactions still prevent truncation of their log records.
Long-running transactions hold the Log Sequence Number (LSN) position, preventing truncation of any log records with lower LSNs. This creates a bottleneck where new transactions pile up behind the oldest active transaction. The log manager cannot advance the MinLSN until all transactions before it complete.
Replication scenarios add complexity. Transactional replication requires the Log Reader Agent to scan committed transactions before truncation occurs. If the agent falls behind or stops, log records accumulate. Always On Availability Groups exhibit similar behavior when secondary replicas cannot keep pace with log hardening.
SQL Server 2019 introduced Accelerated Database Recovery (ADR), which fundamentally changed log truncation behavior. ADR maintains a persistent version store (PVS) inside the user database, allowing faster log truncation regardless of long-running transactions. However, ADR creates its own storage overhead in the user database from the persistent version store.
The log writer process writes log records to disk based on commits, checkpoints, or when the log buffer fills. On high-volume systems, frequent log flushes can create I/O pressure, but the primary concern remains logical space management rather than physical write performance.
AutoDBA checks Transaction log growth patterns, backup frequency optimization, and recovery model configurations across your entire SQL Server instance in 60 seconds. Download the free diagnostic script and see what else needs attention.
Diagnostic Queries
-- Check log space usage and reuse wait descriptions
-- NOTE: sys.dm_db_log_info requires SQL Server 2016 SP2 or SQL Server 2017+
SELECT
d.name AS database_name,
d.log_reuse_wait_desc,
ls.total_log_size_mb,
ls.used_log_space_mb,
ls.used_log_space_pct,
active.active_log_size_mb
FROM sys.databases d
CROSS APPLY (
SELECT
CAST(size * 8.0 / 1024 AS DECIMAL(10,2)) AS total_log_size_mb,
CAST(FILEPROPERTY(name, 'SpaceUsed') * 8.0 / 1024 AS DECIMAL(10,2)) AS used_log_space_mb,
CAST(FILEPROPERTY(name, 'SpaceUsed') * 100.0 / size AS DECIMAL(5,2)) AS used_log_space_pct
FROM sys.master_files
WHERE database_id = d.database_id AND type = 1
) ls
CROSS APPLY (
SELECT CAST(SUM(vlf_size_mb) AS DECIMAL(10,2)) AS active_log_size_mb
FROM sys.dm_db_log_info(d.database_id)
WHERE vlf_status = 2
) active
WHERE d.database_id > 4;
-- Identify long-running transactions blocking log truncation
SELECT
s.session_id,
s.login_name,
s.program_name,
t.transaction_id,
t.name AS transaction_name,
t.transaction_begin_time,
DATEDIFF(MINUTE, t.transaction_begin_time, GETDATE()) AS duration_minutes,
t.transaction_type,
t.transaction_state,
dt.database_transaction_log_bytes_used / 1024 / 1024 AS log_mb_used,
dt.database_transaction_log_bytes_reserved / 1024 / 1024 AS log_mb_reserved
FROM sys.dm_tran_active_transactions t
INNER JOIN sys.dm_tran_session_transactions st ON t.transaction_id = st.transaction_id
INNER JOIN sys.dm_exec_sessions s ON st.session_id = s.session_id
LEFT JOIN sys.dm_tran_database_transactions dt ON t.transaction_id = dt.transaction_id
WHERE t.transaction_begin_time < DATEADD(MINUTE, -30, GETDATE())
ORDER BY t.transaction_begin_time;
-- Check VLF count and status distribution
SELECT
DB_NAME(database_id) AS database_name,
COUNT(*) AS total_vlfs,
SUM(CASE WHEN vlf_status = 2 THEN 1 ELSE 0 END) AS active_vlfs,
SUM(CASE WHEN vlf_status = 0 THEN 1 ELSE 0 END) AS inactive_vlfs,
CAST(SUM(vlf_size_mb) AS DECIMAL(10,2)) AS total_log_size_mb,
CAST(SUM(CASE WHEN vlf_status = 2 THEN vlf_size_mb ELSE 0 END) AS DECIMAL(10,2)) AS active_log_size_mb
FROM sys.dm_db_log_info(DB_ID())
GROUP BY database_id;
-- Monitor recent log backup history and frequency
SELECT TOP 10
d.name AS database_name,
bs.backup_start_date,
bs.backup_finish_date,
DATEDIFF(MINUTE, bs.backup_start_date, bs.backup_finish_date) AS backup_duration_minutes,
CAST(bs.backup_size / 1024.0 / 1024 / 1024 AS DECIMAL(10,2)) AS backup_size_gb,
CAST(bs.compressed_backup_size / 1024.0 / 1024 / 1024 AS DECIMAL(10,2)) AS compressed_size_gb,
bmf.physical_device_name
FROM sys.databases d
INNER JOIN msdb.dbo.backupset bs ON d.name = bs.database_name
INNER JOIN msdb.dbo.backupmediafamily bmf ON bs.media_set_id = bmf.media_set_id
WHERE bs.type = 'L' -- Log backups only
AND d.recovery_model = 1 -- Full recovery model
ORDER BY d.name, bs.backup_start_date DESC;
-- Check replication and Always On AG log consumption
SELECT
d.name AS database_name,
d.log_reuse_wait_desc,
CASE
WHEN d.log_reuse_wait_desc = 'REPLICATION' THEN 'Check Log Reader Agent'
WHEN d.log_reuse_wait_desc = 'AVAILABILITY_REPLICA' THEN 'Check AG secondary sync'
WHEN d.log_reuse_wait_desc = 'ACTIVE_BACKUP_OR_RESTORE' THEN 'Backup/restore in progress'
ELSE 'See log_reuse_wait_desc'
END AS action_required,
CAST(mf.size * 8.0 / 1024 AS DECIMAL(10,2)) AS log_size_mb
FROM sys.databases d
INNER JOIN sys.master_files mf ON d.database_id = mf.database_id AND mf.type = 1
WHERE d.log_reuse_wait_desc NOT IN ('NOTHING', 'CHECKPOINT')
AND d.database_id > 4;
Fix Scripts
Immediate log space relief through log backup:
-- Emergency log backup to free log space
-- WARNING: Only use if database is in FULL recovery model
-- Replace 'YourDatabase' with actual database name
DECLARE @DatabaseName NVARCHAR(128) = 'YourDatabase';
DECLARE @BackupPath NVARCHAR(500) = 'C:\Backups\' + @DatabaseName + '_LogBackup_' +
FORMAT(GETDATE(), 'yyyyMMdd_HHmmss') + '.trn';
-- Verify database is in FULL recovery model
IF (SELECT recovery_model FROM sys.databases WHERE name = @DatabaseName) = 1
BEGIN
BACKUP LOG @DatabaseName TO DISK = @BackupPath
WITH COMPRESSION, CHECKSUM;
PRINT 'Log backup completed to: ' + @BackupPath;
END
ELSE
BEGIN
PRINT 'Database is not in FULL recovery model. Log backup not required.';
END
Immediately frees truncatable log space. Test backup path accessibility first. Monitor log space usage after completion.
Kill long-running transactions blocking log truncation:
-- Identify and optionally terminate blocking transactions
-- CAUTION: This will cause transaction rollback and potential data loss
-- Review transactions carefully before executing
DECLARE @SessionId INT;
DECLARE @TransactionDuration INT = 120; -- Minutes threshold
-- Find sessions with transactions older than threshold
SELECT
@SessionId = s.session_id
FROM sys.dm_tran_active_transactions t
INNER JOIN sys.dm_tran_session_transactions st ON t.transaction_id = st.transaction_id
INNER JOIN sys.dm_exec_sessions s ON st.session_id = s.session_id
WHERE DATEDIFF(MINUTE, t.transaction_begin_time, GETDATE()) > @TransactionDuration
AND s.session_id <> @@SPID -- Don't kill current session
ORDER BY t.transaction_begin_time;
-- Uncomment the following lines only after verifying the session should be terminated
-- PRINT 'Terminating session: ' + CAST(@SessionId AS VARCHAR(10));
-- KILL @SessionId;
Terminates sessions with long-running transactions. Causes transaction rollback. Verify session activity before executing. Use as last resort.
Switch to SIMPLE recovery model temporarily:
-- Temporarily switch to SIMPLE recovery to allow log truncation
-- WARNING: This breaks the log chain for point-in-time recovery
-- Take full backup immediately after switching back to FULL
DECLARE @DatabaseName NVARCHAR(128) = 'YourDatabase';
-- Switch to SIMPLE recovery model
PRINT 'Switching ' + @DatabaseName + ' to SIMPLE recovery model';
EXEC('ALTER DATABASE [' + @DatabaseName + '] SET RECOVERY SIMPLE');
-- Force checkpoint to truncate log
EXEC('CHECKPOINT');
-- Shrink log file if needed (use carefully)
-- EXEC('DBCC SHRINKFILE (''' + @DatabaseName + '_Log'', 1024)'); -- Shrink to 1GB
-- Switch back to FULL recovery model
PRINT 'Switching ' + @DatabaseName + ' back to FULL recovery model';
EXEC('ALTER DATABASE [' + @DatabaseName + '] SET RECOVERY FULL');
PRINT 'IMPORTANT: Take full backup immediately to establish new log chain';
Breaks log chain for point-in-time recovery. Requires immediate full backup afterward. Use only in emergencies when log space is critical.
Configure automatic log file growth:
-- Configure proper log file growth settings
-- Prevents frequent small autogrowth events
DECLARE @DatabaseName NVARCHAR(128) = 'YourDatabase';
DECLARE @LogicalName NVARCHAR(128);
DECLARE @NewSizeMB INT = 2048; -- 2GB initial size
DECLARE @GrowthMB INT = 512; -- 512MB growth increments
-- Get logical log file name
SELECT @LogicalName = name
FROM sys.master_files
WHERE database_id = DB_ID(@DatabaseName) AND type = 1;
-- Set fixed MB growth instead of percentage
EXEC('ALTER DATABASE [' + @DatabaseName + ']
MODIFY FILE (NAME = ''' + @LogicalName + ''',
SIZE = ' + CAST(@NewSizeMB AS VARCHAR(10)) + 'MB,
FILEGROWTH = ' + CAST(@GrowthMB AS VARCHAR(10)) + 'MB)');
PRINT 'Log file configured with ' + CAST(@NewSizeMB AS VARCHAR(10)) + 'MB initial size and ' +
CAST(@GrowthMB AS VARCHAR(10)) + 'MB growth increments';
Prevents performance issues from frequent small growths. Size according to workload requirements. Monitor growth patterns to adjust settings.
AutoDBA generates fix scripts like these automatically, with impact estimates and rollback SQL included.
Prevention
Implement automated log backup schedules based on transaction volume, not calendar intervals. High-volume databases require log backups every 15-30 minutes, while low-volume systems can backup every hour. Monitor log space usage trends to establish appropriate backup frequency.
Configure log file initial size to handle peak transaction loads without autogrowth. Set FILEGROWTH to fixed MB values rather than percentages to avoid exponential growth patterns. Size growth increments based on typical hourly log generation, typically 256MB to 1GB depending on workload.
Establish log space monitoring with alerts at 70% and 85% utilization thresholds. Create automated responses to perform emergency log backups when space exceeds critical levels. Monitor log_reuse_wait_desc changes to identify replication delays, long-running transactions, or backup failures.
For Always On Availability Groups, ensure secondary replicas maintain synchronization and monitor redo queue sizes. Configure proper network bandwidth between replicas and tune redo thread counts on secondaries. Consider using Availability Group distributed transactions sparingly as they increase log retention requirements.
Implement transaction design patterns that minimize log retention. Break large batch operations into smaller transactions with intermediate commits. Use proper indexing strategies to reduce transaction duration and log generation. Consider Read Committed Snapshot Isolation to reduce blocking scenarios that extend transaction duration.
Enable Accelerated Database Recovery on SQL Server 2019+ for databases with frequent long-running transactions or large rollback operations. ADR significantly reduces log space pressure from long-running transactions but requires monitoring of persistent version store growth in the user database.
Establish capacity planning processes that account for log growth patterns during peak usage periods, maintenance operations, and unusual workloads. Document recovery model decisions and ensure backup strategies align with business recovery requirements while maintaining acceptable log space consumption.
Need hands-on help?
Dealing with persistent transaction log growth issues across your environment? Samix Technology provides hands-on SQL Server performance consulting with 15+ years of production DBA experience.