TempDB verstehen, Probleme erkennen und gezielt entlasten
TempDB ist der gemeinsame Arbeitsbereich der ganzen Instanz. Jede Datenbank nutzt sie: für temporäre Tabellen, für Sortierungen, die nicht in den Arbeitsspeicher passen, für Zeilenversionen und für vieles, was im Ausführungsplan gar nicht als TempDB erscheint. Läuft TempDB voll, trifft es alle Anwendungen auf dem Server gleichzeitig, auch die, die das Problem nicht verursacht haben.
Die gute Nachricht: TempDB ist kein schwarzes Loch. SQL Server weiß genau, welche Art von Arbeit wie viel Platz belegt und welche Sitzung oder Transaktion ihn festhält. Diese Seite erklärt die Verbraucher, zeigt mit einer lesenden SQL-Abfrage den aktuellen Stand und führt zu den Vertiefungen. Sie benötigen dafür kein PSG-Werkzeug.
Wer TempDB belegt
SQL Server unterscheidet vier Bereiche. Die Spalten in der Abfrage weiter unten folgen genau dieser Einteilung.
| Bereich | Typische Verursacher | In der Abfrage | Vertiefung |
|---|---|---|---|
| Benutzerobjekte | #temp-Tabellen, Tabellenvariablen, globale ##temp-Tabellen, Indexaufbau mit SORT_IN_TEMPDB | benutzerobjekte_mb | – |
| Interne Objekte | Sortierungen und Hash Joins, die auslagern (Spills), Spools, Cursor, große Zwischenergebnisse | interne_objekte_mb | Spills und Spools |
| Version Store | Read Committed Snapshot, Snapshot Isolation, Trigger, Online-Indexoperationen, sofern ADR für die Datenbank aus ist | version_store_mb | Version Store und Transaktionslog |
| Protokoll von TempDB | Änderungen an temporären Objekten; offene Transaktionen verhindern, dass es wieder frei wird | protokoll_mb, wartegrund | Version Store und Transaktionslog |
Die ersten drei Bereiche liegen in den Datendateien von TempDB, der vierte im Protokoll. Ein volles TempDB-Protokoll (Fehler 9002) und volle Datendateien (Fehler 1105) haben deshalb unterschiedliche Ursachen.
Die Vertiefungen
TempDB einrichten
„Wie viele Dateien, wohin damit, und wie groß?“
Ausführlich, mit LabZum Artikel →02Version Store und Transaktionslog
„TempDB wächst über Stunden, obwohl kaum etwas läuft.“
Ausführlich, mit SQL-Abfrage und LabZum Artikel →03Spills und Spools
„Im Plan steht ein Spool, und TempDB wächst.“
Ausführlich, mit SQL-Abfrage und LabZum Artikel →Welche Beobachtung passt zu Ihnen?
| Ihre Beobachtung | Erster Blick | Vertiefung |
|---|---|---|
| Sie richten TempDB neu ein oder prüfen Dateien und Größe | Anzahl, Größe und Ablage der Dateien prüfen | TempDB einrichten |
| TempDB wächst, und Sie wissen nicht, wodurch | Lagebild, Ergebnis 1: welcher Bereich wächst | Je nach Bereich, siehe oben |
| Eine Abfrage schreibt viel in TempDB, im Plan steht eine Warnung oder ein Spool | Tatsächlichen Ausführungsplan holen | Spills und Spools |
| Der Version Store wächst über Stunden | Lagebild, Ergebnis 3: älteste Snapshot-Transaktion | Version Store und Transaktionslog |
Das TempDB-Protokoll ist voll (Fehler 9002, ACTIVE_TRANSACTION) | Lagebild, Ergebnis 4: offene Transaktionen | Version Store und Transaktionslog |
| Nach einem Neustart ist alles wieder gut | Vor dem nächsten Neustart Belege sichern | Warum hilft ein Neustart? |
Lagebild: Was belegt TempDB gerade?
Die Abfrage liefert eine Momentaufnahme in vier Ergebnissen: die Belegung nach Bereich, die Sitzungen mit dem größten Platzbedarf, den Version Store mit den ältesten Snapshot-Transaktionen und den Zustand des TempDB-Protokolls. Führen Sie sie aus, solange das Problem besteht, bei Bedarf mehrmals im Abstand von einigen Minuten.
Abfrage: Lagebild TempDB
/* PSG Stack · SQL Server TempDB · Abfrage 1
Lagebild TempDB: Wer belegt gerade Platz, und was hält ihn fest?
- Nur lesend. Ändert nichts, beendet keine Sitzung.
- Jede Teilabfrage läuft mit MAXDOP 1.
- Liefert vier Ergebnisse:
1 Belegung der TempDB-Datendateien nach Art
2 Sitzungen mit der größten TempDB-Belegung
3 Version Store: Größe je Datenbank und älteste Snapshot-Transaktionen
4 TempDB-Protokoll: Füllstand, Wartegrund und offene Transaktionen
- Rechte: VIEW SERVER STATE
(ab SQL Server 2022 genügt VIEW SERVER PERFORMANCE STATE).
Ohne dieses Recht scheitert jeder Teil mit Fehler 300 (VIEW DATABASE STATE allein genügt nicht).
- Ab SQL Server 2017. Geprüft im PSG-Lab auf 2017, 2019, 2022 und 2025.
- Läuft in jeder Datenbank: TempDB-Werte kommen immer aus TempDB.
- Momentaufnahme: Werte ändern sich laufend. Bei Bedarf mehrmals ausführen.
*/
/* 1 · Belegung der TempDB-Datendateien nach Art (MB) */
SELECT
SUM(fsu.user_object_reserved_page_count) * 8 / 1024 AS benutzerobjekte_mb,
SUM(fsu.internal_object_reserved_page_count) * 8 / 1024 AS interne_objekte_mb,
SUM(fsu.version_store_reserved_page_count) * 8 / 1024 AS version_store_mb,
SUM(fsu.mixed_extent_page_count) * 8 / 1024 AS gemischte_extents_mb,
SUM(fsu.unallocated_extent_page_count) * 8 / 1024 AS frei_mb,
COUNT(*) AS datendateien
FROM tempdb.sys.dm_db_file_space_usage AS fsu
OPTION (MAXDOP 1);
/* 2 · Sitzungen mit der größten TempDB-Belegung
Abgeschlossene Tasks (session_space_usage) plus laufende Tasks (task_space_usage).
Benutzerobjekte: #temp-Tabellen, Tabellenvariablen, Indexaufbau mit SORT_IN_TEMPDB.
Interne Objekte: Sort, Hash, Spools, Cursor, LOB-Zwischenergebnisse.
Die Zähler gelten je Sitzung: Löscht eine andere Sitzung ein Objekt (z. B. eine globale ##-Tabelle),
bleibt der Platz der erstellenden Sitzung zugerechnet, bis diese endet. Die Summe kann daher
über Ergebnis 1 liegen. */
WITH belegung AS (
SELECT ssu.session_id,
ssu.user_objects_alloc_page_count - ssu.user_objects_dealloc_page_count AS benutzer_seiten,
ssu.internal_objects_alloc_page_count - ssu.internal_objects_dealloc_page_count AS interne_seiten
FROM sys.dm_db_session_space_usage AS ssu
WHERE ssu.database_id = 2
UNION ALL
SELECT tsu.session_id,
tsu.user_objects_alloc_page_count - tsu.user_objects_dealloc_page_count,
tsu.internal_objects_alloc_page_count - tsu.internal_objects_dealloc_page_count
FROM sys.dm_db_task_space_usage AS tsu
WHERE tsu.database_id = 2
), je_sitzung AS (
SELECT session_id,
SUM(benutzer_seiten) AS benutzer_seiten,
SUM(interne_seiten) AS interne_seiten
FROM belegung
GROUP BY session_id
)
SELECT TOP (20)
js.session_id,
es.login_name,
es.host_name,
es.program_name,
es.status,
CAST(CASE WHEN js.benutzer_seiten > 0 THEN js.benutzer_seiten ELSE 0 END * 8 / 1024.0 AS decimal(12,1)) AS benutzerobjekte_mb,
CAST(CASE WHEN js.interne_seiten > 0 THEN js.interne_seiten ELSE 0 END * 8 / 1024.0 AS decimal(12,1)) AS interne_objekte_mb,
r.command,
r.wait_type,
r.granted_query_memory * 8 / 1024 AS speicherzusage_mb,
DB_NAME(r.database_id) AS datenbank,
LEFT(st.text, 4000) AS sql_text
FROM je_sitzung AS js
JOIN sys.dm_exec_sessions AS es ON es.session_id = js.session_id
LEFT JOIN sys.dm_exec_requests AS r ON r.session_id = js.session_id
OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS st
WHERE es.is_user_process = 1
AND (js.benutzer_seiten > 0 OR js.interne_seiten > 0)
ORDER BY (CASE WHEN js.benutzer_seiten > 0 THEN js.benutzer_seiten ELSE 0 END
+ CASE WHEN js.interne_seiten > 0 THEN js.interne_seiten ELSE 0 END) DESC
OPTION (MAXDOP 1);
/* 3a · Version Store in TempDB je Datenbank (MB) */
SELECT
DB_NAME(vs.database_id) AS datenbank,
vs.reserved_page_count * 8 / 1024 AS version_store_mb
FROM sys.dm_tran_version_store_space_usage AS vs
WHERE vs.reserved_page_count > 0
ORDER BY vs.reserved_page_count DESC
OPTION (MAXDOP 1);
/* 3b · Älteste Snapshot-Transaktionen: Sie halten den Version Store fest
snapshot_isolation = 0: Transaktion ohne SNAPSHOT-Isolation, die Zeilenversionen erzeugt
(z. B. Schreiber unter READ_COMMITTED_SNAPSHOT). */
SELECT TOP (10)
ast.session_id,
ast.elapsed_time_seconds AS sekunden_offen,
ast.is_snapshot AS snapshot_isolation,
es.login_name,
es.host_name,
es.program_name,
es.status,
es.last_request_end_time AS letzte_anfrage_beendet
FROM sys.dm_tran_active_snapshot_database_transactions AS ast
LEFT JOIN sys.dm_exec_sessions AS es ON es.session_id = ast.session_id
ORDER BY ast.elapsed_time_seconds DESC
OPTION (MAXDOP 1);
/* 4a · TempDB-Protokoll: Größe, Füllstand und Wartegrund */
SELECT
CAST(ls.total_log_size_in_bytes / 1048576.0 AS decimal(12,1)) AS protokoll_mb,
CAST(ls.used_log_space_in_percent AS decimal(5,1)) AS belegt_prozent,
d.log_reuse_wait_desc AS wartegrund
FROM tempdb.sys.dm_db_log_space_usage AS ls
CROSS JOIN sys.databases AS d
WHERE d.database_id = 2
OPTION (MAXDOP 1);
/* 4b · Offene Transaktionen mit TempDB-Protokoll, größte zuerst */
SELECT TOP (10)
st.session_id,
dt.database_transaction_begin_time AS begonnen,
DATEDIFF(SECOND, dt.database_transaction_begin_time, SYSDATETIME()) AS sekunden_offen,
CAST(dt.database_transaction_log_bytes_used / 1048576.0 AS decimal(12,1)) AS protokoll_genutzt_mb,
CAST(dt.database_transaction_log_bytes_reserved / 1048576.0 AS decimal(12,1)) AS protokoll_reserviert_mb,
es.login_name,
es.program_name,
es.status
FROM sys.dm_tran_database_transactions AS dt
JOIN sys.dm_tran_session_transactions AS st ON st.transaction_id = dt.transaction_id
LEFT JOIN sys.dm_exec_sessions AS es ON es.session_id = st.session_id
WHERE dt.database_id = 2
ORDER BY dt.database_transaction_log_bytes_used DESC
OPTION (MAXDOP 1);So lesen Sie das Ergebnis:
- Ergebnis 1 zeigt, welcher Bereich wächst. Steht der Platz bei
interne_objekte_mb, suchen Sie in Ergebnis 2 nach laufenden Abfragen mit großer Speicherzusage oder großen Sortierungen. Steht er beiversion_store_mb, hilft Ergebnis 2 meist nicht weiter: Versionen werden keiner Sitzung zugerechnet. - Ergebnis 2 nennt die Sitzungen mit eigenem Platzbedarf. Eine schlafende Sitzung mit vielen Benutzerobjekten hält oft eine große
#temp-Tabelle, die erst mit der Verbindung verschwindet. Die Zähler gelten je Sitzung: Löscht eine andere Sitzung ein Objekt, etwa eine globale##-Tabelle, bleibt der Platz der erstellenden Sitzung zugerechnet, bis diese endet. Die Summe kann deshalb über Ergebnis 1 liegen. - Ergebnis 3 zeigt, für welche Datenbank Versionen entstehen und welche Snapshot-Transaktion sie am längsten festhält.
sekunden_offenüber Stunden bei Status „sleeping“ ist ein typischer Halter. - Ergebnis 4 zeigt, warum das TempDB-Protokoll nicht frei wird. Bei
ACTIVE_TRANSACTIONsteht die Ursache in der Liste der offenen Transaktionen. Ist das Protokoll erst wenig gefüllt, steht dort trotz offener Transaktion oft nochNOTHING; maßgeblich ist dann die Liste in 4b.
Beispielausgabe aus einem nachgestellten Lab-Lauf (SQL Server 2025, synthetische Daten, Namen frei gewählt, Spalten gekürzt). Sitzung 56 hat eine #temp-Tabelle gefüllt und ihre Transaktion offen gelassen, Sitzung 54 hält einen offenen Cursor, Sitzung 53 liest in SNAPSHOT-Isolation, ohne abzuschließen:
1 · Belegung nach Art (MB)
benutzerobjekte_mb interne_objekte_mb version_store_mb frei_mb
40 48 53 127
2 · Sitzungen mit der größten Belegung
session_id status benutzerobjekte_mb interne_objekte_mb
54 sleeping 0.0 42.4
56 sleeping 39.2 0.0
60 running 0.0 6.0
3b · Älteste Snapshot-Transaktionen
session_id sekunden_offen snapshot_isolation status
53 25 1 sleeping
4a · TempDB-Protokoll
protokoll_mb belegt_prozent wartegrund
136.0 99.5 ACTIVE_TRANSACTION
4b · Offene Transaktionen mit TempDB-Protokoll
session_id sekunden_offen protokoll_genutzt_mb status
56 20 78.0 sleepingGelesen: Der Version Store (53 MB) gehört keiner Sitzung in Ergebnis 2, festgehalten wird er von Sitzung 53 in Ergebnis 3b. Das Protokoll ist fast voll, weil Sitzung 56 ihre Transaktion nicht beendet. Beide Sitzungen schlafen: Sie arbeiten nicht, sie halten nur fest.
Drei verbreitete Annahmen, die nicht stimmen
„Temporäre Tabellen werden nicht protokolliert.“ Änderungen in TempDB werden protokolliert, nur in geringerem Umfang als in Benutzerdatenbanken: Sie müssen zurückgerollt, aber nicht nach einem Absturz wiederhergestellt werden. Eine lange Transaktion, die in #temp-Tabellen schreibt, kann das TempDB-Protokoll deshalb genauso füllen wie jede andere.
„Die Sitzung mit dem meisten TempDB-Platz ist der Verursacher.“ Für Benutzer- und interne Objekte stimmt das meist. Für den Version Store nicht: Die Versionen erzeugen die schreibenden Sitzungen, festgehalten werden sie von der ältesten Transaktion, die sie noch lesen könnte. Diese hat selbst oft keine einzige Seite belegt.
„Verkleinern löst das Problem.“ Solange die Ursache besteht, wächst TempDB wieder. Ein Verkleinern im laufenden Betrieb kann zudem an belegten Seiten scheitern. Microsoft beschreibt die Möglichkeiten und Grenzen in einem eigenen Artikel (siehe Quellen). Finden und beenden Sie zuerst, was den Platz belegt.
Was dauerhaft entlastet
- TempDB passend einrichten. Mehrere gleich große Datendateien und ausreichend Platz von Anfang an vermeiden Wartezeiten bei der Platzvergabe und Wachstum zur Unzeit. Wie viele Dateien, wohin und wie groß, zeigt TempDB einrichten. Das ersetzt nicht, die Verursacher zu kennen.
- Arbeit vermeiden statt Platz schaffen. Ein passender Index erspart Sortierungen und temporäre Indizes, eine bessere Schätzung verhindert Spills. Wie das im Ausführungsplan aussieht, zeigt Spills und Spools.
- Transaktionen kurz halten. Jede offene Transaktion hält Versionen und Protokoll fest. Mehr dazu in Version Store und Transaktionslog.
- Temporäre Tabellen gezielt einsetzen. Eine
#temp-Tabelle mit wenigen, gefilterten Zeilen kann eine Abfrage stark vereinfachen. Eine#temp-Tabelle, die eine halbe Tabelle kopiert, verlagert nur Arbeit nach TempDB. - Den Blick weiten. Ist TempDB nur ein Symptom und der Server insgesamt langsam, führt der SQL Server Performance Ratgeber durch die passenden Diagnosewege.
Quellen
- Microsoft: Die TempDB-Datenbank
- Microsoft: TempDB verkleinern
- Microsoft:
sys.dm_db_task_space_usage - Microsoft: Transaktionssperren und Zeilenversionierung