Fix it now
tempdb filled with row versions and your transaction was chosen as the one to sacrifice. The version store holds previous images of rows so that readers under row versioning see a consistent picture without blocking writers, and cleanup cannot pass the oldest transaction that might still need them. One forgotten transaction fills a large tempdb with everybody else’s versions.
USE tempdb;
SELECT SUM(version_store_reserved_page_count) * 8 / 1024 AS version_mb,
SUM(user_object_reserved_page_count) * 8 / 1024 AS user_mb,
SUM(internal_object_reserved_page_count) * 8 / 1024 AS internal_mb
FROM sys.dm_db_file_space_usage;
SELECT session_id, transaction_id, transaction_sequence_num, elapsed_time_seconds
FROM sys.dm_tran_active_snapshot_database_transactions
ORDER BY elapsed_time_seconds DESC;
- If the version figure dominates, this is your problem. If internal objects dominate instead, you are looking at sorts and hashes spilling out of memory, which is a query tuning problem and a different article.
- Take the top session from the second query and see what it is doing:
SELECT session_id, status, command, last_request_start_time FROM sys.dm_exec_requests WHERE session_id = <spid>; - End it if it is abandoned, or let it finish if it is genuine work. Cleanup resumes once the oldest transaction is gone.
- Confirm which versioning options are on:
SELECT name, snapshot_isolation_state_desc, is_read_committed_snapshot_on FROM sys.databases; - Give tempdb room: pre-size its files on a volume of their own, with fixed growth increments rather than percentages.
internal_object_reserved_page_count is only meaningful in tempdb, because internal objects exist nowhere else. Run the first query in the tempdb context or the numbers will not mean what you think.
If the version store is falling and the workload is running, you are done. If it fills again tomorrow, the next section explains why cleanup stalls.
Why it happens
When row versioning is in use, updating or deleting a row copies its previous image into the version store in tempdb and leaves a pointer behind. A reader that started earlier follows the chain back to the version it is entitled to see. That is what allows readers not to block writers, and it is the mechanism behind both read committed snapshot isolation and explicit snapshot isolation. Microsoft also lists the other features that generate versions in the same store: online index operations, multiple active result sets, and AFTER triggers.
A background task removes versions that no transaction can still need, and it works out that boundary from the oldest active transaction. A single long-running one therefore holds the whole store open. The store then grows at the rate of your write workload, not at the rate of anything the long transaction is doing, which is why an idle session with an open transaction can fill tempdb overnight while appearing to do nothing at all.
The related codes are not all about capacity, and that is worth being precise about. 3959 is the version store reporting itself full and warning that a transaction needing it may be rolled back. 3958 is a reader that went looking for a versioned row and did not find it. 3966 is a transaction being rolled back because it was marked as a victim earlier, when the version store was shrunk. 3964 is different in kind: it means a DDL statement was issued inside a snapshot isolation transaction, which is not allowed. It has nothing to do with tempdb capacity and it will not be fixed by adding space.
One long transaction is preventing cleanup
You have this one if The snapshot transactions view shows a session open for hours, and the version store keeps growing while it exists.
- Identify the owner:
SELECT session_id, login_name, host_name, program_name FROM sys.dm_exec_sessions WHERE session_id = <spid>; - Decide with them whether it can be ended. KILL rolls it back, and a large rollback takes time and resources.
- Look for the pattern behind it: a transaction left open across user input, one opened before a slow external call, or a forgotten BEGIN TRANSACTION in a query window.
- Confirm the version store starts shrinking once the transaction is gone.
tempdb is too small or cannot grow
You have this one if The version store is large but not unreasonable, and tempdb’s files are at their maximum or the volume is full.
- Pre-size the tempdb data files rather than relying on growth, and give every data file the same initial size and growth settings.
- Set fixed growth increments in megabytes and remove percentage growth.
- Put tempdb on its own volume where you can, so a busy hour does not fill the volume holding user databases.
- Restart the instance after changing file sizes. tempdb is recreated at startup from the settings you configure.
Microsoft’s stated reason for identical file sizes is the proportional-fill algorithm: allocations favour the file with the most free space, so unequal files produce unequal load.
Read committed snapshot isolation was enabled without sizing for it
You have this one if The problem appeared after someone turned on RCSI to reduce blocking, and tempdb has not changed since.
- Confirm it is on with the sys.databases query above.
- Size tempdb for the write volume of the busiest database, not for its idle state.
- Monitor the version store size and the version generation and cleanup rates through the Transactions performance counters over a full business cycle.
- If the workload turns out not to need it, turn it off deliberately:
ALTER DATABASE [MyDb] SET READ_COMMITTED_SNAPSHOT OFF WITH ROLLBACK IMMEDIATE;disconnects users at that moment.
Long reporting queries against the transactional database
You have this one if Version pressure peaks when reports run, and the sessions holding the oldest transactions are read-only.
- Move the reporting workload off the transactional instance where you can, to a restored copy or a readable secondary.
- Break long report transactions into shorter ones so the cleanup boundary keeps advancing.
- Check for reports holding one transaction open across the whole run without needing to.
Accelerated Database Recovery, available from SQL Server 2019 and off by default, keeps a persistent version store on the PRIMARY filegroup of the user database rather than in tempdb. Enabling it changes this picture for that database.
3964, which is not a space problem at all
You have this one if The message says a DDL statement is not allowed inside a snapshot isolation transaction, and no tempdb figure is unusual.
- Move the DDL out of the snapshot isolation transaction. This is a code change, not a configuration change.
- Check whether the session set SNAPSHOT isolation explicitly, or whether it inherited it.
- Do not add tempdb space in response to this one. It will not help.
Full reference
What is actually filling tempdb
| What dominates tempdb | What it means |
|---|---|
| Version store pages | Row versioning. This article’s problem; find the oldest transaction |
| Internal object pages | Sorts, hashes and spools spilling out of memory. A query tuning problem |
| User object pages | Temporary tables and table variables created by application code |
| Nothing dominates but tempdb is small | A sizing problem. tempdb was never given room for this workload |
| A session with a huge elapsed_time_seconds in the snapshot transactions view | That is your blocker |
The five codes, and which are about space
| Code | Published meaning | About space? |
|---|---|---|
| 3967 | Insufficient space in tempdb to hold row versions. Need to shrink the version store to free up some space | Yes |
| 3958 | Transaction aborted when accessing a versioned row. The requested versioned row was not found | Indirectly: the version was already cleaned up |
| 3966 | Transaction is rolled back when accessing the version store. It was earlier marked as victim when the version store was shrunk | Yes, as an after-effect |
| 3959 | Version store is full. New versions could not be added. A transaction that needs to access the version store may be rolled back | Yes |
| 3964 | Transaction failed because this DDL statement is not allowed inside a snapshot isolation transaction | No. This is a code problem |
What generates versions besides your isolation level
- Data modifications in a database using read committed snapshot isolation or snapshot isolation.
- Online index operations, which version rows while the index is being built.
- Multiple active result sets, which needs versions to keep interleaved result sets consistent.
- AFTER triggers, which read the previous image of the modified rows.
- So a database with no snapshot isolation at all can still put pressure on the version store, and the first three of those are easy to overlook when the isolation level looks innocent.
Accelerated Database Recovery, and where its versions live
Accelerated Database Recovery is available from SQL Server 2019 and is off by default. When it is enabled on a user database, its persistent version store lives on the PRIMARY filegroup of that database rather than in tempdb, which takes that database’s versioning pressure off tempdb entirely and puts it on the user database’s own files instead. That is a trade rather than a saving, and it needs to be planned for in the data file sizing.
In SQL Server 2025 there is a further wrinkle worth knowing before you size anything. When Accelerated Database Recovery is enabled in tempdb itself, tempdb holds two independent version stores: the traditional one, used for versions generated by user databases that do not have ADR enabled, and a persistent version store used for versions generated by transactions in tempdb. Microsoft’s guidance is to allocate enough space in the tempdb data files for both.
Sizing tempdb from measurement
- Watch the version store size across a full business cycle, not a quiet afternoon, using the Transactions performance counters and the file space usage query above.
- Take the maximum you observe and adjust it for the concurrency you expect, then set the file sizes to that.
- Give every data file the same initial size and growth settings, because the proportional-fill algorithm favours the file with the most free space.
- Use fixed increments in megabytes, never percentages.
- Remember that tempdb is recreated from those settings at every start, so configure the size you want rather than growing into it each day.
- If ADR is enabled in tempdb on SQL Server 2025, size for both version stores.
When a licence is the actual fix
Almost every 3967 is solved by ending a long transaction and sizing tempdb properly, and neither costs anything. Where an edition genuinely becomes the ceiling is at the bottom of the range: in SQL Server 2025 Express caps the maximum relational database size at 50 GB, limits compute to the lesser of one socket or four cores, and caps the buffer pool at 1,410 MB. A version-heavy workload on those numbers spends its life short of memory, which pushes more work through tempdb rather than less. If that is where you are running, Arco supplies SQL Server 2025 Standard in two-core packs and can work out how many packs your server needs. Deal with the transaction and the tempdb files first; only look at the licence if the edition itself is what is stopping you.
Every code this article covers
| Code | What it points at | Source |
|---|---|---|
3967 |
Insufficient space in tempdb to hold row versions. The version store needs to be shrunk to free space in tempdb | Microsoft Learn |
3958 |
Transaction aborted when accessing a versioned row in a named table and database. The requested versioned row was not found | Microsoft Learn |
3966 |
Transaction is rolled back when accessing the version store. It was earlier marked as a victim when the version store was shrunk | Microsoft Learn |
3959 |
Version store is full. New versions could not be added. A transaction that needs to access the version store may be rolled back | Microsoft Learn |
3964 |
Transaction failed because this DDL statement is not allowed inside a snapshot isolation transaction. Not a capacity problem | Microsoft Learn |
Confirm the fix worked
- Re-run the tempdb usage query and confirm the version store figure is falling.
- Confirm no session in
sys.dm_tran_active_snapshot_database_transactionshas an unreasonable elapsed_time_seconds. - Check the error log for further 3967 or 3959 entries after the change.
- Confirm tempdb’s files are at the sizes you configured after the next instance restart, and that they are all equal.
- Watch the version store across a full business cycle before concluding it is sized correctly.
Questions people ask about this
Will a licence or a bigger edition fix this?
Not directly. No edition makes tempdb bigger or cleans the version store sooner, and the fix is nearly always operational. Edition only matters if you are on Express, whose SQL Server 2025 limits – 50 GB per database, the lesser of one socket or four cores, and 1,410 MB of buffer pool – are the real constraint.
I got 3964 and adding tempdb space did nothing. Why?
Because 3964 is not a capacity error. Its published meaning is that a DDL statement is not allowed inside a snapshot isolation transaction. It is a code change, not a configuration change, and it happens to share a number range with the version store errors.
How big should tempdb be for row versioning?
Big enough for the versions generated between the start and end of your longest legitimate transaction, plus normal sort and temporary table use. Measure it from the version store counters across a full week rather than guessing, and leave headroom. On SQL Server 2025 with Accelerated Database Recovery enabled in tempdb, size for two version stores.
Is it safe to kill the long transaction?
It is safe in that the database stays consistent, because the transaction rolls back. It is not free: the rollback can take as long as the work it is undoing, and it holds resources while it runs.
Does turning off snapshot isolation solve it?
It removes that source of versions, but it brings back the blocking that made someone enable it, and it does not stop online index operations, multiple active result sets or AFTER triggers generating versions. Treat it as a workload decision, not a fix, and make it deliberately with the application team.
