powershelldba.de · Uwe Janke

Log Truncation vs. Log Shrinking: What Each One Actually Does

Two words that sound like the same thing and are not. Getting them mixed up is the reason people shrink a log file that was never too big, feel relieved for a day, and watch it grow straight back.

The one-sentence answer

Truncation frees space inside the log file. Shrinking makes the log file smaller.

Truncation happens automatically, is what you want, and does not change the size of anything on disk. Shrinking is something you do by hand, gives disk space back to Windows, and is almost never the right response to a log problem.

The car park

Picture the transaction log as a multi-storey car park with a fixed number of spaces. The building is the log file. Each space is a chunk of log, and SQL Server fills them in order, then wraps around to the beginning and reuses the ones that have been freed.

Which leads to the mistake that this entire article exists for: people respond to a full log by shrinking it. That is demolishing floors of a building that is full because the exits are blocked.

What truncation really is

The log file is divided internally into virtual log files, usually called VLFs. Truncation is SQL Server marking one or more VLFs as inactive, which means their content is no longer needed and the space can be written over.

A VLF can only be marked inactive once nothing still needs it. When it happens depends on the recovery model:

Recovery modelTruncation happens
SIMPLEAt every checkpoint, automatically. No log backups exist or are possible.
FULLOnly when you take a log backup. A full backup does not truncate the log.
BULK_LOGGEDSame as FULL: only on log backup.

That middle row is responsible for most emergency calls about a log filling a disk. The database is in FULL recovery, somebody set up nightly full backups and no log backups at all, and the log has been growing since the day the database went live because nothing has ever told SQL Server the log records are safe to discard.

A full backup does not truncate the log. If you are in FULL recovery and never take log backups, you are in FULL recovery in name only, and you are paying for it in disk space.

The query that tells you the actual cause

Before touching anything, ask SQL Server why it cannot reuse the log. There is a column for exactly this:

SELECT name,
       recovery_model_desc,
       log_reuse_wait_desc
FROM   sys.databases
WHERE  name = 'AppDB';

And to see how full the log actually is, rather than how big the file is:

USE AppDB;
GO
SELECT total_log_size_in_bytes / 1048576.0 AS log_file_mb,
       used_log_space_in_bytes  / 1048576.0 AS used_mb,
       used_log_space_in_percent           AS used_pct
FROM   sys.dm_db_log_space_usage;

A 200 GB log file that is 3% used is not a truncation problem. It is a file that was allowed to grow once and never came back down, which is the one case where shrinking is legitimate. A 20 GB log file that is 99% used is a truncation problem, and shrinking it would achieve nothing at all.

What log_reuse_wait_desc is telling you

ValueWhat it meansWhat to do
NOTHINGLog can be reused right nowNothing. Your log size is a sizing question, not a truncation one.
LOG_BACKUPFULL or BULK_LOGGED, and no log backup since the last truncationTake a log backup. By far the most common answer.
ACTIVE_TRANSACTIONAn open transaction is pinning the logFind it and commit or roll it back. See below.
CHECKPOINTWaiting for a checkpointUsually transient. Common on SIMPLE with heavy write activity.
ACTIVE_BACKUP_OR_RESTOREA backup or restore is runningWait for it to finish.
AVAILABILITY_REPLICAA secondary has not hardened the log yetFix the AG synchronization, not the log.
REPLICATIONThe log reader has not picked up the changesCheck the log reader agent, or a stale publication nobody uses any more.
DATABASE_MIRRORINGThe mirror is behind or disconnectedFix the mirroring session.
XTP_CHECKPOINTIn-memory OLTP checkpoint behindRare, and its own topic.

For ACTIVE_TRANSACTION, find the culprit:

DBCC OPENTRAN('AppDB');

-- or, with more detail:
SELECT s.session_id, s.login_name, s.host_name, s.program_name,
       t.database_transaction_begin_time,
       t.database_transaction_log_bytes_used / 1048576.0 AS log_used_mb
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('AppDB')
ORDER  BY t.database_transaction_begin_time;

A transaction opened three days ago by an application that has since been restarted will hold the entire log hostage, and no amount of shrinking will release it. The related failure mode in tempdb is covered in The Real Fix for an Orphaned Transaction That Fills TempDB.

Why routine shrinking is a bad habit

Shrinking a log is not neutral, and the damage is not the same as shrinking a data file. There are three distinct costs.

1. It grows straight back

The log is as big as it is because at some point the workload needed it that big. Index maintenance, a monthly batch job, a large delete. Shrink it and the next run of that same workload grows it again, so you have spent I/O to achieve nothing that lasts past Tuesday.

2. Log growth cannot use instant file initialization

Data files can be created and grown instantly when the service account holds the Perform Volume Maintenance Tasks right. Log files never can. Every byte of new log space is zero-filled first, always. That means a growth event on a busy system is slow, and transactions that need log space wait for it to finish. Repeatedly shrinking a log is therefore repeatedly scheduling a stall for later.

3. It shreds the log into small VLFs

Every growth event adds a set of VLFs. Shrink and grow in small steps often enough and you end up with thousands of tiny ones. That slows down crash recovery, database startup, log backups and restores, and availability group synchronization. Count them:

SELECT COUNT(*) AS vlf_count
FROM   sys.dm_db_log_info(DB_ID('AppDB'));

A few dozen to a few hundred is normal. Several thousand means somebody has been shrinking, or autogrowth is set to a tiny increment, or both. On anything older than SQL Server 2016 SP2 the DMV does not exist and you are back to counting rows from DBCC LOGINFO.

Three things people try that make it worse Switching to SIMPLE and back. It empties the log and breaks the backup chain at the same time. Point-in-time recovery is gone until the next full backup, which nobody remembers to take. Take a log backup instead.

Looking for TRUNCATE_ONLY or NO_LOG. Those options were removed in SQL Server 2008 precisely because they silently destroyed recoverability. Anything on the internet still recommending them is at least seventeen years old.

Adding a second log file. A valid emergency move when the disk is full and you need the database back right now. It is not a fix, it does not make anything faster (SQL Server writes to the log sequentially, not in parallel), and the second file should be removed once the real cause is dealt with.

When shrinking is genuinely correct

There is a real case for it: something exceptional and non-recurring inflated the log far past its normal working size. A one-off bulk load, an index rebuild on the largest table in the database, a runaway transaction that has since been resolved. The log is now 300 GB, the workload has never needed more than 20 GB, and that space is worth reclaiming.

The right sequence is truncate, then shrink, then grow back once to the size you actually want:

-- 1. Make the space reusable (FULL/BULK_LOGGED only; SIMPLE does this itself)
BACKUP LOG AppDB TO DISK = 'D:\Backup\AppDB_log.trn';

-- 2. Shrink the file down
USE AppDB;
GO
DBCC SHRINKFILE (AppDB_log, 1024);   -- target in MB, deliberately small

-- 3. Grow it back to the right size in ONE step, so you get few, large VLFs
ALTER DATABASE AppDB
  MODIFY FILE (NAME = AppDB_log, SIZE = 20480MB);

-- 4. Sane autogrowth as a safety net, not as a sizing strategy
ALTER DATABASE AppDB
  MODIFY FILE (NAME = AppDB_log, FILEGROWTH = 1024MB);

Step 3 is the one people skip, and it is the one that matters. Shrinking and then letting autogrowth crawl the file back up in small increments is how you get the VLF problem described above. Decide the size, set it once, and leave it alone.

Percentage-based autogrowth is a trap for the same reason: 10% of a small log is a pointless increment, and 10% of a large log is a very long zero-fill at the worst possible moment. Use a fixed number of megabytes.

How big should the log actually be? Big enough for the largest single unit of work between two log backups, plus headroom. In practice that is usually your index maintenance window, not your application. If a nightly rebuild generates 18 GB of log, a 4 GB log file will grow every night no matter how often you shrink it. The measurement approach is in How Much Log Does REORGANIZE Really Generate?, and the batching technique that avoids the problem entirely is in Batch Deletion of Large Data Volumes.

The short version, one more time

 TruncationShrinking
What it doesMarks log space reusableReturns disk space to Windows
File size on diskUnchangedSmaller
Who triggers itSQL Server, automaticallyYou, manually
HowCheckpoint (SIMPLE) or log backup (FULL)DBCC SHRINKFILE
Fixes "log is full"Yes, this is the fixNo
Safe to do routinelyYes, it already is routineNo

If you remember one thing: when the log fills up, the question is never "how do I make this file smaller." It is "what is stopping SQL Server from reusing the space it already has," and log_reuse_wait_desc answers that in one query.

Questions people actually ask

Q: What is the difference between truncating and shrinking the transaction log? Truncation marks space inside the file as reusable and leaves the file exactly the same size. Shrinking physically reduces the file and hands that space back to Windows. Truncation is automatic and is what you want; shrinking is manual and is usually a mistake.
Q: Why does my log keep growing even though I shrink it every week? Because shrinking never addressed the reason the space could not be reused. Check log_reuse_wait_desc. In the vast majority of cases it says LOG_BACKUP, which means the database is in FULL recovery and nobody is taking log backups. Fix that and the growth stops on its own.
Q: Does a full backup truncate the log? No. In FULL and BULK_LOGGED recovery, only a log backup truncates. This single misunderstanding is behind a large share of "the log filled the drive overnight" incidents.
Q: Can I just switch to SIMPLE and back to clear it? It works, and it breaks your log backup chain, so you lose point-in-time recovery until the next full backup. It is a data-loss risk wearing the costume of a maintenance step. Take a log backup instead.
Q: My log file is huge but only 2% used. Is that a problem? Not an urgent one. That is a sizing question, not a truncation one. If a one-off event inflated it and the space is genuinely needed elsewhere, shrink it once and immediately grow it back to a sensible fixed size so you do not end up with thousands of small VLFs.
Related reading SQL Server Backup Concepts for how recovery models and backup types fit together. ALTER COLUMN on Large Tables for a classic way to inflate a log by accident. How AlwaysOn Really Works: The Log Flow Behind Every Commit for why an unhealthy secondary shows up as a log that will not truncate. Fixing Databases Stuck in RESTORING Mode for the other end of the same story.