The one-sentence answer
The recovery model controls when transaction log records become reusable. That is the whole mechanism. Every advantage and every disadvantage below is a consequence of that one rule.
- SIMPLE: log records are thrown away automatically at every checkpoint. Cheap, self-managing, and you cannot restore to a point in time.
- FULL: log records are kept until a log backup copies them out. You can restore to a specific second, and your log grows without limit if nobody takes those log backups.
- BULK_LOGGED: FULL, except that bulk operations are minimally logged. Faster bulk loads, at the price of losing point-in-time restore across the window where those operations happened.
Notice that none of those descriptions mention backups being taken or not taken. The recovery model does not take backups. It decides what your backups are capable of.
Why "recovery model" is a misleading name
The name suggests a recovery strategy. It is really a log retention policy. A database in FULL recovery with no log backups scheduled is not safer than one in SIMPLE. It is strictly worse: you get the same data loss exposure, plus a transaction log that grows until the disk fills.
This is the single most common recovery model mistake in the field. Somebody reads that FULL is the "safe" one, sets every database to FULL, schedules a nightly full backup, and goes home. Three weeks later the log file is 400 GB and the application is down with a log-full error. The recovery model was never the protection. The log backup schedule was.
A recovery model is a promise about what you could restore. Only the backup schedule makes that promise real.
Side-by-side comparison
| Property | SIMPLE | FULL | BULK_LOGGED |
|---|---|---|---|
| Log truncation | Automatic, at checkpoint | Only after a log backup | Only after a log backup |
| Log backups | Not possible | Required | Required |
| Point-in-time restore | No | Yes | Not across minimally logged operations |
| Tail-log backup | No | Yes | Only if no minimally logged operations since the last log backup |
| Worst-case data loss | Everything since the last full or differential backup | Everything since the last log backup, often seconds | Everything since the last log backup |
| Bulk operations | Minimally logged | Fully logged | Minimally logged |
| Works in an Availability Group | No | Yes | No |
| Works with log shipping | No | Yes | Yes, with the caveats below |
| Admin effort | Almost none | Real, ongoing | Real, plus a switching discipline |
SIMPLE
At every checkpoint, SQL Server marks the inactive part of the log as reusable. Nothing is retained for backup purposes. The log file stays roughly the size of your largest concurrent transaction, and it manages itself.
Advantages
- No log backup schedule to build, monitor or fix. One entire category of 3am pages disappears.
- The transaction log stays small and predictable without intervention.
- Bulk operations are minimally logged, so loads and index rebuilds are fast and cheap on log I/O.
- Less backup storage, fewer files, a shorter restore chain, and a restore procedure a junior can follow under pressure.
Disadvantages
- No point-in-time restore. Your recovery point is the last full or differential backup, full stop.
- No tail-log backup. When the server dies, the work since the last backup is simply gone.
- Rules out Availability Groups, database mirroring and log shipping.
- Your RPO is your backup interval. Nightly fulls means up to 24 hours of loss.
When SIMPLE is the right call
When the data can be reconstructed from somewhere else, or when losing a day of it costs less than running a log backup chain. In practice:
- Development and test databases.
- Data warehouse and staging databases that are reloaded from source systems. If the ETL can simply run again, log backups are protecting something that is already protected. See fact and dimension tables in a star schema for the kind of database this usually applies to.
- Read-only or read-mostly reporting copies that are refreshed from a master.
- Scratch and sandbox databases.
FULL
Every operation is fully logged, and log records stay in the log until a log backup copies them out. That retained chain is what makes point-in-time restore possible: restore the last full backup, then the differentials, then replay log backups up to a chosen second.
Advantages
- Point-in-time restore. You can stop at a timestamp, an LSN, or a named mark, which is what you want the morning somebody drops a table at 09:14.
- Tail-log backup. If the data files are lost but the log file survives, you can back up the tail of the log and lose almost nothing.
- RPO becomes a scheduling decision rather than a technical limit. Log backups every 5 minutes means at most 5 minutes of exposure.
- Required for Availability Groups, mirroring and log shipping. If high availability is anywhere on the roadmap, this is the only option.
Disadvantages
- You must take log backups. Not should. If you do not, the log grows until the disk is full and the database stops accepting writes.
- More storage, more backup files, more moving parts to monitor.
- Restores are longer and more complex, because you are replaying a chain rather than restoring one file.
- Bulk operations are fully logged, so a large load or index rebuild can generate an enormous amount of log. This is exactly the pain that BULK_LOGGED exists to solve.
When FULL is the right call
Any database holding data that cannot be reconstructed, which in most shops means every production database that users type into. Also any database in an Availability Group, and anything under a regulatory retention or auditability requirement, where "we lost a day" is not an acceptable sentence in an incident report.
BULK_LOGGED
BULK_LOGGED behaves like FULL, with one exception: bulk operations are minimally logged. Instead of logging every row, SQL Server logs the extent allocations and flags the changed extents. Typical candidates are BULK INSERT, bcp, SELECT ... INTO, CREATE INDEX and ALTER INDEX ... REBUILD.
The effect on a big load is dramatic: an operation that would write tens of gigabytes of log in FULL can write a small fraction of that.
Advantages
- Bulk loads and index rebuilds run considerably faster and generate far less log.
- The log file does not have to be pre-sized for the worst bulk operation of the month.
- Unlike switching to SIMPLE, switching between FULL and BULK_LOGGED does not break the log chain.
Disadvantages
These are the reason BULK_LOGGED is a temporary switch and not a setting you leave on:
- No point-in-time restore inside an affected log backup. You can restore that log backup in full, but you cannot stop partway through it. If a bad
DELETEran during that window, your options are "before the window" or "after the window", nothing in between. - Log backups get bigger, not smaller. This surprises people. Minimal logging shrinks the log file growth, but the log backup must include the data extents that were changed so the restore can reproduce them. You moved the volume from the log file into the log backup.
- Tail-log backup can become impossible. If the data files are lost and minimally logged operations happened since the last log backup, the tail-log backup needs those data extents and cannot get them. You lose everything back to the previous log backup. This is the one that actually hurts.
When BULK_LOGGED is the right call
As a deliberate, bounded switch around a known bulk operation, in a maintenance window, on a database that is otherwise in FULL. The discipline that makes it safe:
-- 1. Close the chain cleanly before switching
BACKUP LOG [MyDb] TO DISK = 'L:\Backup\MyDb_pre_bulk.trn';
-- 2. Switch
ALTER DATABASE [MyDb] SET RECOVERY BULK_LOGGED;
-- 3. Run the bulk operation here
-- 4. Switch straight back
ALTER DATABASE [MyDb] SET RECOVERY FULL;
-- 5. Immediately re-establish point-in-time capability
BACKUP LOG [MyDb] TO DISK = 'L:\Backup\MyDb_post_bulk.trn';
Steps 1 and 5 are the point of the whole exercise. They confine the "no point-in-time restore" gap to the bulk window itself, instead of letting it contaminate an arbitrary stretch of your log chain.
The pseudo-SIMPLE trap
This one deserves its own section because it silently defeats an otherwise correct configuration.
A database set to FULL recovery that has never had a full backup taken does not behave as FULL. It keeps truncating its log at checkpoint, exactly like SIMPLE. There is no log chain, because a log chain has to start somewhere, and it starts at the first full backup.
So a freshly created database, set to FULL on day one, with log backups dutifully scheduled, is not protected at all until that first full backup runs. Every log backup before it fails. The setting says FULL. The behaviour is SIMPLE. This state is commonly called pseudo-SIMPLE.
You can see it directly:
SELECT d.name,
d.recovery_model_desc,
d.log_reuse_wait_desc,
drs.last_log_backup_lsn
FROM sys.databases d
JOIN sys.database_recovery_status drs
ON drs.database_id = d.database_id
WHERE d.recovery_model_desc <> 'SIMPLE';
A last_log_backup_lsn of NULL on a database that claims to be in FULL recovery means no log chain exists yet. Take a full backup and it starts.
Switching between models
Switching is not symmetric, and the asymmetry matters.
| Switch | Effect on the log chain | What you must do |
|---|---|---|
| FULL to BULK_LOGGED | Chain survives | Take a log backup first, so the pre-bulk state is recoverable |
| BULK_LOGGED to FULL | Chain survives | Take a log backup immediately, to restore point-in-time capability |
| FULL to SIMPLE | Chain is broken | Take a log backup before switching, or lose the ability to recover that window |
| SIMPLE to FULL | No chain until the next full or differential backup | Take a full or differential backup immediately, otherwise you are in pseudo-SIMPLE |
The trap is the round trip. Somebody switches a database to SIMPLE to deal with a full log, the incident calms down, and they switch it back to FULL and consider the matter closed. It is not closed. Until a full or differential backup runs, that database is unprotected, and the log backup job may be failing quietly in the background. Switching to SIMPLE to fix a full log is almost always the wrong move anyway; see log truncation vs. log shrinking for what to do instead.
Finding out what you actually have
Inventory first, opinions second. This lists every database with its model, why its log cannot be reused right now, and how long since its last full and log backup:
SELECT d.name,
d.recovery_model_desc,
d.log_reuse_wait_desc,
MAX(CASE WHEN b.type = 'D' THEN b.backup_finish_date END) AS last_full,
MAX(CASE WHEN b.type = 'L' THEN b.backup_finish_date END) AS last_log
FROM sys.databases d
LEFT JOIN msdb.dbo.backupset b
ON b.database_name = d.name
WHERE d.database_id > 4
GROUP BY d.name, d.recovery_model_desc, d.log_reuse_wait_desc
ORDER BY d.recovery_model_desc, d.name;
Two patterns in that output are worth acting on immediately:
- FULL with
last_logempty or stale. This database has the cost of FULL and none of the benefit. Either start taking log backups or move it to SIMPLE deliberately. log_reuse_wait_desc = 'LOG_BACKUP'. The log is waiting for a backup that is not coming. This is the most common cause of a growing log and it is a scheduling problem, not a disk problem.
master, msdb and model have their own rules, and tempdb is always SIMPLE and cannot be changed. Note that model is worth checking separately: its recovery model is inherited by every database created afterwards, so a wrong setting there quietly propagates.
Choosing, in practice
Three questions, in order. They resolve most cases without debate.
- If this database was lost, could somebody rebuild its contents from another system? If yes, and doing so is genuinely routine rather than theoretical, SIMPLE is defensible.
- Is it in an Availability Group, mirrored, or log shipped, or might it be within a year? If yes, FULL. There is no decision to make.
- How much data is the business willing to lose? If the honest answer is less than a day, you need FULL and a log backup interval that matches the number. If nobody can answer, that is the real finding, and it belongs in the risk register rather than in a database setting.
BULK_LOGGED is not in that list on purpose. It is not a steady-state answer to "which model should this database use". It is a tool you reach for during a specific operation and put down afterwards.
Related reading
- SQL Server backup concepts, for how full, differential and log backups combine into a restore chain.
- Differential backups, COPY_ONLY and the full restore, including why a stray full backup can break a differential chain.
- Log truncation vs. log shrinking, the distinction behind most "the log is full" incidents.
- Restore and recovery mode, for what
NORECOVERYandSTANDBYactually do during a restore chain. - Log shipping, the boring DR option, which is built directly on the log chain FULL recovery gives you.
The short version
Pick SIMPLE when the data is reproducible and you would rather not run a log chain. Pick FULL when it is not, and then actually schedule the log backups, because the setting alone protects nothing. Use BULK_LOGGED as a switch around a bulk operation, with a log backup on each side of it, and switch back when you are done.
And check for pseudo-SIMPLE. A database that says FULL and has never had a full backup is not protected, and it will not tell you so.