SQL Server DBCC SHRINKDATABASE: When and When Not to Shrink

A one-time data load doubled a data file overnight, the load is done, the rows are deleted, and the file is now mostly empty space. The reflex — reach for DBCC SHRINKDATABASE and give the disk back — is one of the most common self-inflicted performance wounds in SQL Server administration. This post explains exactly what shrinking does to your indexes, when it is genuinely the right call, and how to reclaim space the few times you must without wrecking performance.

Purpose and Overview

Shrinking a data file physically relocates pages from the end of the file to the first available free space near the front — and in doing so it fragments every index it touches, frequently leaving the database measurably slower than it was before the space was reclaimed. The algorithm is mechanical and indifferent to logical order: it grabs whatever page sits at the high end of the file and drops it into the lowest free slot, scrambling the contiguous page chains that range scans depend on. You reclaim gigabytes of disk and pay for it in logical fragmentation that can run to 90%+ on large indexes.

DBCC SHRINKDATABASE applies this operation to every file in a database at once; DBCC SHRINKFILE targets a single file and gives you finer control, including a no-fragmentation option. Both exist for legitimate reasons, but those reasons are narrow: a genuine, permanent reduction in data volume, a server being decommissioned or downsized, or reclaiming space after data was deliberately moved to another filegroup. None of those describe routine maintenance, which is exactly where shrink most often gets misused.

The crucial distinction is between trailing free space and interior free space. If the empty space sits at the end of the file, you can release it cheaply with no page movement at all. If it is interspersed through the file, reclaiming it requires the destructive page relocation — and even then, autogrowth will simply re-grow the file the next time the data expands, so you have paid the fragmentation cost for nothing.

Code Breakdown

Before shrinking anything, measure how much free space actually exists and where. Then prefer the TRUNCATEONLY form, which is non-destructive.

 1-- 1. How much free space per file, and is it worth reclaiming?
 2SELECT
 3    f.name                                              AS logical_name,
 4    f.type_desc,
 5    CAST(f.size / 128.0 AS decimal(10,2))               AS size_mb,
 6    CAST(FILEPROPERTY(f.name, 'SpaceUsed') / 128.0
 7         AS decimal(10,2))                              AS used_mb,
 8    CAST((f.size - FILEPROPERTY(f.name, 'SpaceUsed')) / 128.0
 9         AS decimal(10,2))                              AS free_mb
10FROM sys.database_files AS f;
11
12-- 2. NON-DESTRUCTIVE: release only trailing free space. No page movement,
13--    no fragmentation. Try this first.
14DBCC SHRINKFILE (N'YourDatabase_Data', TRUNCATEONLY);
15
16-- 3. DESTRUCTIVE: shrink to a target size, relocating pages and
17--    fragmenting indexes. Use only when truly justified.
18DBCC SHRINKFILE (N'YourDatabase_Data', 4096);   -- target 4096 MB

The free-space query

FILEPROPERTY(name, 'SpaceUsed') returns used pages, and sys.database_files.size returns total allocated pages; both are in 8 KB pages, so dividing by 128 converts to megabytes. The free_mb column tells you whether there is enough reclaimable space to justify any action at all — if it is small, stop here.

SHRINKFILE vs SHRINKDATABASE

SHRINKDATABASE shrinks every file in the database, which is rarely what you want and offers no per-file control. SHRINKFILE lets you target the one file that genuinely has reclaimable space, specify a precise target size, and — critically — use TRUNCATEONLY. Always prefer SHRINKFILE.

TRUNCATEONLY

DBCC SHRINKFILE (name, TRUNCATEONLY) releases only the free space at the physical end of the file back to the operating system. It performs no page relocation, so it causes no fragmentation. If the free space happens to be trailing — common right after a TRUNCATE TABLE or a large delete from recently-added pages — this gives you the disk back for free. It is the only shrink form that should ever be reflexive.

The Fragmentation Cost

The targeted-size form in step 3 is where the damage lives. Relocating pages to compact the file shuffles them out of logical order, so index range scans that used to read contiguous pages now chase scattered ones. The fix afterward is to rebuild the affected indexes — which re-grows the file you just shrank, because a rebuild needs working space. Shrink-then-rebuild-then-the-file-grows-back is the futile cycle that makes routine shrinking pointless and harmful.

You can see the damage directly. Capture avg_fragmentation_in_percent from sys.dm_db_index_physical_stats before and after a destructive shrink and the largest indexes routinely jump from single-digit fragmentation to 90% or more:

1SELECT i.name AS index_name,
2       ps.avg_fragmentation_in_percent,
3       ps.page_count
4FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') AS ps
5JOIN sys.indexes AS i
6     ON i.object_id = ps.object_id AND i.index_id = ps.index_id
7WHERE ps.page_count > 1000
8ORDER BY ps.avg_fragmentation_in_percent DESC;

That measured jump is the cost you are accepting, and it is why the only safe sequence is shrink first, then rebuild — never the reverse.

Key Benefits and Use Cases

  • Permanent data reduction — after archiving or purging a large fraction of a database for good, a one-time shrink right-sizes the files.
  • Decommissioning and downsizing — reclaiming space on a server being retired or migrated to smaller storage.
  • Post-filegroup-move cleanup — after relocating data to another filegroup, the emptied file can be shrunk or removed.
  • Trailing-space reclaimTRUNCATEONLY safely returns end-of-file free space with zero fragmentation cost.
  • Emergency disk relief — as a stopgap when a volume is critically full and no other space exists, accepting the fragmentation as a known debt.
  • Log file correctionSHRINKFILE on a transaction log that ballooned during an unusual operation, once it is back to normal.

Performance Considerations

  • Massive index fragmentation: the page-relocation forms can drive logical fragmentation toward 100% on large indexes.
  • Heavy I/O and logging: shrinking moves a lot of pages, generating substantial transaction-log activity, especially in FULL recovery.
  • Blocking: shrink takes schema and page locks that can block concurrent workload on a busy database.
  • Autogrowth undoes it: if the data grows back, the file re-grows, so you fragmented indexes to reclaim space you immediately lost.
  • Churn: routine shrink-and-grow cycles waste I/O and keep indexes perpetually fragmented.

Practical Tips

  • Never schedule shrink as routine maintenance — it is a deliberate, justified one-off, not a nightly job.
  • Never put SHRINK after a rebuild in a maintenance plan — you would fragment exactly the indexes you just defragmented.
  • Prefer TRUNCATEONLY — try the non-destructive form first; it often reclaims what you need with no downside.
  • Rebuild indexes after a necessary shrink — if you had to relocate pages, plan the index rebuild as the immediate follow-up.
  • Right-size files instead — set sensible initial sizes and autogrowth increments so files stop ballooning in the first place.

Conclusion

DBCC SHRINKDATABASE and SHRINKFILE are emergency and decommissioning tools, not maintenance tools. Measure first, prefer TRUNCATEONLY, and when a destructive shrink is genuinely warranted, rebuild the affected indexes right after. For the full toolkit, see the complete DBCC command reference.

References

Posts in this series