Change Tracking (CT) in SQL Server is a lightweight mechanism for tracking data changes without the overhead of full auditing or CDC. However, when troubleshooting CT cleanup behavior, one issue that often surprises engineers is the absence of the MSchange_tracking_history table—a system table expected to exist when auto-cleanup is running normally. In this blog, we walk through the scenarios that can lead to a missing history table, what to validate in your environment, and how to collect the right data for deeper investigation.
- Confirming Whether Auto-Cleanup Has Run Successfully
When diagnosing Change Tracking cleanup issues, the first step is verifying whether the auto‑cleanup process has executed at least once on the affected database.
Here are key questions to ask:
✔ Was the auto‑cleanup process ever triggered?
SQL Server's CT cleanup runs automatically in the background based on internal scheduling. You should validate whether it has executed at least once since CT was enabled (or re‑enabled).
✔ Were there rows outside the retention period at the time cleanup ran?
The cleanup task only deletes rows older than the configured retention period.
If no such rows existed, cleanup may have run—but removed nothing.
✔ Did the cleanup task complete successfully?
It’s important to know whether cleanup:
- Started,
- Scanned eligible rows,
- Deleted entries, and
- Completed without interruption.
If cleanup is running but not making progress, SQL Server provides internal diagnostics that can help determine whether the process is blocked, paused, or skipped.
- Why the MSchange_tracking_history Table May Be Missing
Microsoft’s Product Team has clarified an important behavioral detail:
➡ The MSchange_tracking_history table is created only by the auto-cleanup process.
This means the table may be missing if:
🔸 CT was disabled previously
If Change Tracking was turned off at any point, SQL Server removes internal structures associated with CT.
🔸 CT was later re-enabled, but auto-cleanup never ran
Re‑enabling CT does not immediately recreate the history table.
It will only be created once auto-cleanup executes naturally for the first time.
🔸 Only manual cleanup was performed
Manual cleanup (using internal stored procedures or scripts) does not generate the MSchange_tracking_history table.
Only the automated engine-driven cleanup does.
➡ Resolution:
Ensuring that the auto-cleanup process runs at least once is typically enough to recreate the missing history table.
- How to Collect Deeper Diagnostics: SQL Server Profiler Trace
For deeper analysis—especially when the cleanup process appears stalled, not deleting rows, or not creating the history table—you should collect a SQL Server Profiler trace that captures a full auto-cleanup cycle.
What the trace helps with:
- Verifying which CT internal procedures are being executed.
- Determining whether the cleanup scans and deletion operations occur.
- Identifying blocking, timeouts, or internal errors.
- Confirming if the engine attempts to initialize or update the history table.
When to collect the trace:
- Run it during an auto-cleanup cycle.
- Ensure that the cycle actually deletes records, not just scans without action.
- This allows the Product Team or DBA staff to analyze real cleanup behavior from the engine side.
T‑SQL: Check Change Tracking Cleanup Status
These queries help you verify whether auto‑cleanup has been running, whether it’s enabled, and whether there are expired records (outside your retention window) still present in CT side tables or syscommittab. The approach aligns with Microsoft’s troubleshooting guidance.
- Is auto‑cleanup enabled? What’s the retention?
-- Run in the target database
SELECT
db.database_id,
DB_NAME(db.database_id) AS database_name,
db.is_auto_cleanup_on, -- 1 = ON, 0 = OFF
db.retention_period,
db.retention_period_units -- 1 = MINUTES, 2 = HOURS, 3 = DAYS
FROM sys.change_tracking_databases AS db
WHERE db.database_id = DB_ID();
This view reports whether auto‑cleanup is ON and shows the retention.
2) Has auto‑cleanup run recently? (History table)
-- History is created only when auto‑cleanup runs
SELECT TOP (1000)
start_time,
end_time,
cleanup_version, -- engine cleanup version recorded
comments
FROM dbo.MSchange_tracking_history
ORDER BY start_time DESC;
If dbo.MSchange_tracking_history doesn’t exist or is empty, auto‑cleanup most likely has never run (or hasn’t run since CT was re‑enabled). The table itself is created by the auto‑cleanup process.
Note: The missing MSchange_tracking_history table can occur if CT was disabled and re‑enabled but auto‑cleanup hasn’t run since; manual cleanup alone won’t create it. A single successful auto‑cleanup cycle will create the table.
3) Are there rows outside the retention window? (Per‑table scan)
The following script checks each CT‑enabled table and estimates whether its side table contains rows older than the current min valid version (i.e., candidates for cleanup):
/*
This script enumerates CT-enabled tables, derives the min_valid_version,
and checks whether rows exist with SYS_CHANGE_VERSION < min_valid_version.
It uses dynamic SQL because each CT side table is an internal table named:
change_tracking_<object_id>
See Microsoft guidance on CT side tables.
*/
SET NOCOUNT ON;
DECLARE @sql NVARCHAR(MAX) = N'';
DECLARE @CRLF NCHAR(2) = NCHAR(13) + NCHAR(10);
;WITH ct AS (
SELECT
t.object_id,
QUOTENAME(OBJECT_SCHEMA_NAME(t.object_id)) + N'.' + QUOTENAME(OBJECT_NAME(t.object_id)) AS table_name,
t.min_valid_version
FROM sys.change_tracking_tables AS t
)
SELECT @sql = STRING_AGG(CONVERT(NVARCHAR(MAX),
N'
BEGIN TRY
DECLARE tbl SYSNAME = N''' + table_name + N''';
DECLARE @oid INT = ' + CAST(object_id AS NVARCHAR(20)) + N';
DECLARE @side SYSNAME = N''change_tracking_'' + CAST(@oid AS NVARCHAR(20));
DECLARE @minver BIGINT = ' + CAST(min_valid_version AS NVARCHAR(20)) + N';
-- Build dynamic statement to count expired rows in the side table
DECLARE @dyn NVARCHAR(MAX) = N''
SELECT
''''''+@tbl+'''''' AS ct_table,
''''''+@side+'''''' AS side_table,
@minver AS min_valid_version,
COUNT(*) AS expired_rows
FROM '' + QUOTENAME(@side) + N''
WHERE SYS_CHANGE_VERSION < @minver'';
DECLARE @params NVARCHAR(400) = N''@minver BIGINT'';
EXEC sp_executesql @dyn, @params, @minver = @minver;
END TRY
BEGIN CATCH
SELECT
''' + table_name + N''' AS ct_table,
ERROR_NUMBER() AS error_number,
ERROR_MESSAGE() AS error_message;
END CATCH' ), @CRLF )
FROM ct;
-- Execute the generator
EXEC sys.sp_executesql @sql;
How it works:
- CT stores changes in internal “side tables” (named change_tracking_<object_id>), and the minimum valid version per CT‑enabled table is tracked in sys.change_tracking_tables. Rows with SYS_CHANGE_VERSION below min_valid_version are eligible for cleanup and should be deleted by auto‑cleanup; the script surfaces a count of such rows.
4) Check the commit table for expired records (syscommittab)
Auto‑cleanup also clears entries in the commit table (syscommittab) once they fall outside retention. This quick check compares the latest commit version with your tables’ min_valid_version to see if commit entries are lagging:
-- Approximate commit backlog vs. min_valid_version
SELECT
DB_NAME() AS database_name,
MAX(ctv.commit_ts) AS latest_commit_ts,
MAX(ctv.commit_lsn) AS latest_commit_lsn
FROM sys.syscommittab AS ctv; -- internal commit table used by CT
If you see a large backlog in syscommittab relative to table min_valid_version, it can indicate cleanup isn’t keeping up or hasn’t run. (Microsoft’s troubleshooting doc lists syscommittab growth as a symptom when cleanup is stalled.)
5) Optional: Force a manual cleanup (diagnostics only)
Use manual cleanup with caution and preferably when AUTO_CLEANUP = OFF, to avoid contention with the background task.
-- Flush commit table in batches (example batch size = 100,000)
EXEC sys.sp_flush_commit_table_on_demand delete_batch_size = 100000;
-- Flush a specific CT internal table (replace with your user table name)
EXEC sys.sp_flush_CT_internal_table_on_demand @table_name = N'dbo.YourTable';
Manual cleanup procedures are documented in community guidance and Microsoft KBs; prefer auto‑cleanup for normal operation.
Relevant Behaviors & Known Issues
History table creation: dbo.MSchange_tracking_history is created only by the auto‑cleanup process. If CT was disabled and later re‑enabled but only manual cleanup has run since, the history table won’t exist until one successful auto‑cleanup cycle completes. [learn.microsoft.com]
“Auto‑cleanup runs but isn’t making progress”: Follow Microsoft’s troubleshooting flow; symptoms include many rows outside retention in side tables or syscommittab. [learn.microsoft.com]
Fixes and trace flags: Certain builds of SQL Server 2016/2017/2019 had issues where cleanup versions could go negative or overflow INT in the history table. Microsoft provides KBs and guidance (e.g., TF 8290) to reset invalid cleanup versions and CU updates to fix history table datatype problems. If you suspect these issues, review the KBs and apply the relevant CUs
Summary
A missing MSchange_tracking_history table often indicates that Change Tracking was disabled at some point and auto-cleanup has not run since re-enabling it. Auto-cleanup is the only mechanism that creates this table—manual cleanup will not.
To resolve or further investigate:
- Confirm that auto-cleanup has executed at least once.
- Validate whether cleanup deleted rows beyond the retention period.
- Capture a SQL Server Profiler trace for a successful cleanup cycle.
This ensures complete visibility into the CT engine behavior and helps determine whether remediation is needed.
Official Reference
Microsoft Learn — Troubleshoot change tracking auto‑cleanup issues (status, history table, retention, syscommittab symptoms):
learn.microsoft.com/sql/.../cleanup-and-troubleshoot-change-tracking-sql-server'
Microsoft Support — KB4538365 (negative cleanup versions; TF 8290 guidance):
support.microsoft.com/.../kb4538365
Microsoft Support — KB5007039 (INT overflow in MSchange_tracking_history cleanup_version; CU fix):
support.microsoft.com/.../kb5007039
Community references explaining CT side tables and cleanup mechanics:
mssqltips.com — Change Tracking performance troubleshooting
Stack Overflow — manual cleanup examples & cautions