Memory-Optimized TempDB? RESOURCE_SEMAPHORE verstehen und beheben

Warum es keine speicheroptimierte TempDB gibt, wie Sie trotzdem TempDB-Last reduzieren können – und was der RESOURCE_SEMAPHORE mit Arbeitsspeicher zu tun hat

📌 Gibt es eine "Memory-Optimized TempDB"?

KLARSTELLUNG
Kurze Antwort: Nein. Die TempDB selbst kann nicht als speicheroptimierte Datenbank deklariert werden. Es gibt keinen Schalter wie ALTER DATABASE tempdb SET MEMORY_OPTIMIZED = ON. Die TempDB bleibt eine klassische, auf Festplatten basierende Systemdatenbank mit diskbasierten Tabellen (auch für temporäre Objekte). Was viele Administratoren jedoch meinen, ist die Nutzung von speicheroptimierten Tabellenvariablen (MEMORY_OPTIMIZED = ON auf Tabellenvariablenebene) oder die Nutzung von In-Memory OLTP, um bestimmte Arbeitslasten aus der TempDB auszulagern. Dieser Artikel klärt die Möglichkeiten und Grenzen.
💡 Kernaussage: Sie können die TempDB nicht "in den Speicher verschieben". Aber Sie können speicheroptimierte Tabellen und Tabellenvariablen erstellen, die die TempDB-Allokationen vermeiden – das reduziert Konflikte auf GAM/SGAM-Seiten und entlastet die TempDB signifikant.

⚡ Speicheroptimierte Tabellenvariablen – TempDB-Entlastung pur

IN-MEMORY OLTP
Seit SQL Server 2016 können Sie Tabellenvariablen als speicheroptimiert deklarieren. Solche Variablen werden nicht in der TempDB abgelegt, sondern ausschließlich im Arbeitsspeicher verwaltet (mit optionaler Dauerhaftigkeit). Das eliminiert TempDB-Allokationen, Latch-Konflikte und reduziert die I/O-Last. Besonders nützlich für ETL-Zwischenspeicher, Session-State oder als Ersatz für kleine temporäre Tabellen.
-- Klassische Tabellenvariable (nutzt TempDB) 
DECLARE @TempTable TABLE (Id INT, Name NVARCHAR(100)); 
 
-- Speicheroptimierte Tabellenvariable (keine TempDB-Nutzung) 
DECLARE @MemOptTable TABLE ( 
Id INT NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = 10000), 
Name NVARCHAR(100) NOT NULL 
) WITH (MEMORY_OPTIMIZED = ON); 
 
-- Einfügen und Abfragen wie gewohnt 
INSERT INTO @MemOptTable VALUES (1, N'Demo'); 
SELECT * FROM @MemOptTable;
📌 Voraussetzungen: Die Datenbank muss eine MEMORY_OPTIMIZED_DATA-Dateigruppe besitzen (wie bei jeder In-Memory OLTP-Nutzung). Die Tabellenvariable selbst ist nicht dauerhaft; bei Verlust der Datenbankverbindung ist ihr Inhalt weg. Das ist aber meist erwünscht.
🚀 Performancegewinn: Tests zeigen eine Reduzierung der TempDB-Allokationen um 80–100 % für diese Variablen. Zudem entfallen Wartezeiten wie PAGELATCH_UP auf TempDB-Seiten. Besonders vorteilhaft in hochparallelen Umgebungen mit vielen kurzen temporären Tabellen.

⏳ RESOURCE_SEMAPHORE – Der Wartetyp für Speichermangel

WAIT STATISTICS
Der Wartetyp RESOURCE_SEMAPHORE tritt auf, wenn eine Abfrage auf die Zuteilung von Arbeitsspeicher wartet. SQL Server vergibt Speicher für Sortierungen, Hash-Joins, Indexerstellung und andere speicherintensive Operationen. Ist der verfügbare Arbeitsspeicher knapp oder überschreitet die angeforderte Speichermenge das konfigurierte Limit (max server memory), müssen Abfragen warten – sichtbar in sys.dm_os_wait_stats als RESOURCE_SEMAPHORE.
Typische Ursachen:
  • Unzureichend dimensionierter Arbeitsspeicher für die Arbeitslast.
  • Nicht optimierte Abfragen mit riesigen Sortier- oder Hash-Operationen (z. B. fehlende Indizes, zu viele ORDER BY ohne Index).
  • Übermäßige Parallelität (max degree of parallelism zu hoch) – jeder parallele Thread fordert eigenen Speicher an.
  • Zu niedrige max server memory-Einstellung.
-- RESOURCE_SEMAPHORE Wartezeiten anzeigen 
SELECT  
wait_type, 
waiting_tasks_count, 
wait_time_ms, 
max_wait_time_ms, 
signal_wait_time_ms 
FROM sys.dm_os_wait_stats 
WHERE wait_type = 'RESOURCE_SEMAPHORE'; 
 
-- Aktuelle Speicherzuteilungen für Abfragen 
SELECT  
session_id, 
requested_memory_kb, 
granted_memory_kb, 
required_memory_kb, 
used_memory_kb 
FROM sys.dm_exec_query_memory_grants 
WHERE grant_time IS NULL; -- Wartende Abfragen
🚨 Notfallmaßnahme: Wenn viele Abfragen auf RESOURCE_SEMAPHORE warten, kann ein vorübergehendes Absenken der Parallelität (OPTION (MAXDOP 1) für kritische Abfragen) helfen. Die dauerhafte Lösung ist die Optimierung der Abfragen oder die Erhöhung des Arbeitsspeichers (sowohl physisch als auch die SQL Server-Einstellung max server memory).

🔗 Verbindung: TempDB, Arbeitsspeicher und RESOURCE_SEMAPHORE

ZUSAMMENHANG
Auf den ersten Blick sind TempDB-Kontention (Seiten-Latches) und RESOURCE_SEMAPHORE (Speichermangel) unterschiedliche Probleme. Dennoch können sie sich gegenseitig verstärken:
  • Wenn die TempDB langsam ist (z. B. durch Latch-Konflikte), können Abfragen länger laufen und halten damit auch ihren zugewiesenen Arbeitsspeicher länger belegt – das erhöht den Druck auf das Speichersystem und kann RESOURCE_SEMAPHORE auslösen.
  • Umgekehrt: Bei Speichermangel versucht SQL Server, mehr Operationen in die TempDB auszulagern (z. B. Sortierungen oder Hash-Joins, die nicht vollständig in den Speicher passen). Das belastet die TempDB zusätzlich und verstärkt Kontention.
  • Der Einsatz speicheroptimierter Tabellenvariablen entlastet sowohl die TempDB (weniger Seitenallokationen) als auch den allgemeinen Speicherdruck (weil diese Variablen effizienter sind als diskbasierte temporäre Tabellen).
🔍 Diagnose-Check: Prüfen Sie, ob hohe RESOURCE_SEMAPHORE-Wartezeiten mit hohen PAGELATCH_*-Wartezeiten auf TempDB-Seiten zusammenfallen. Wenn ja, sollten Sie beide Probleme gleichzeitig angehen: TempDB optimieren (mehrere Dateien, SSDs) und Speicher konfigurieren.

🧪 Dauerhafte speicheroptimierte Tabellen als TempDB-Ersatz

ADVANCED
In Szenarien, in denen Sie viele temporäre Daten zwischen verschiedenen Sessions teilen müssen (ähnlich einer globalen temporären Tabelle), können Sie eine dauerhafte, speicheroptimierte Tabelle mit SCHEMA_ONLY Dauerhaftigkeit anlegen. Diese Tabelle verhält sich dann wie eine global verfügbare, nicht persistente Arbeitstabelle – jedoch ohne TempDB-Overhead.
-- Dauerhafte speicheroptimierte Tabelle (nur Schema, keine Datenpersistenz) 
CREATE TABLE dbo.SharedWorkMemory ( 
WorkId INT NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = 100000), 
SessionId INT NOT NULL, 
Payload VARBINARY(8000), 
INDEX ix_Session NONCLUSTERED (SessionId) 
) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_ONLY); 
 
-- Session A schreibt Daten 
INSERT INTO dbo.SharedWorkMemory (WorkId, SessionId, Payload) 
VALUES (1, @@SPID, 0x1234); 
 
-- Session B liest (geeignete Synchronisation erforderlich!) 
SELECT * FROM dbo.SharedWorkMemory WHERE SessionId = 42;
💡 Wichtig: Da diese Tabelle von mehreren Sessions gleichzeitig genutzt werden kann, müssen Sie selbst für die Bereinigung veralteter Daten sorgen (z. B. über einen Agent-Job). Sie ersetzt nicht die automatische Bereinigung von temporären Tabellen. Trotzdem ist sie ein mächtiges Werkzeug, um TempDB zu umgehen.

🛠️ RESOURCE_SEMAPHORE beheben – Praktische Maßnahmen

TROUBLESHOOTING
1. Speicherkonfiguration prüfen: Stellen Sie sicher, dass max server memory nicht zu niedrig angesetzt ist. In einer reinen SQL Server-Umgebung (keine anderen großen Anwendungen) kann der Wert bis auf 80–90 % des physischen RAMs gesetzt werden.
-- Aktuelle Speicherkonfiguration anzeigen 
SELECT name, value_in_use  
FROM sys.configurations  
WHERE name IN ('max server memory (MB)', 'min server memory (MB)'); 
 
-- Erhöhen (Beispiel: 24 GB) 
EXEC sp_configure 'max server memory', 24576; 
RECONFIGURE;
2. Abfragen optimieren: Identifizieren Sie die speicherhungrigsten Abfragen mit sys.dm_exec_query_memory_grants. Oft fehlen Indizes, die Sortierungen oder Hash-Joins erzwingen.
-- Abfragen mit hohem Speicherbedarf finden 
SELECT TOP 10 
q.text, 
mg.requested_memory_kb, 
mg.granted_memory_kb, 
mg.query_cost 
FROM sys.dm_exec_query_memory_grants mg 
CROSS APPLY sys.dm_exec_sql_text(mg.sql_handle) q 
ORDER BY mg.requested_memory_kb DESC;
3. Parallelität reduzieren: Eine zu hohe Parallelität (z. B. MAXDOP 0 auf einem 64-Kern-Server) kann jeden einzelnen Thread mit Speicher versorgen. Setzen Sie max degree of parallelism auf einen vernünftigen Wert (z. B. 4 oder 8).
-- MAXDOP global setzen 
EXEC sp_configure 'max degree of parallelism', 4; 
RECONFIGURE; 
 
-- Oder für eine bestimmte Abfrage 
SELECT ... OPTION (MAXDOP 1);
⚠️ Wichtig: Bevor Sie den Arbeitsspeicher erhöhen, prüfen Sie, ob die Abfragen selbst optimiert werden können. Mehr Speicher kaschiert nur schlechtes Design, bis die Datenmenge weiter wächst.

📊 Monitoring für In-Memory OLTP und TempDB-Entlastung

DMVS
Um zu messen, wie stark Sie die TempDB durch speicheroptimierte Tabellen entlastet haben, und um RESOURCE_SEMAPHORE frühzeitig zu erkennen, nutzen Sie folgende Abfragen:
-- TempDB-Seitenallokationen pro Session (vorher/nachher Vergleich) 
SELECT  
session_id, 
SUM(internal_objects_alloc_page_count) AS AllocPages, 
SUM(user_objects_alloc_page_count) AS UserAllocPages 
FROM sys.dm_db_task_space_usage 
GROUP BY session_id 
ORDER BY AllocPages DESC; 
 
-- Speicher, der von speicheroptimierten Tabellen belegt wird 
SELECT  
OBJECT_NAME(object_id) AS TableName, 
SUM(memory_allocated_for_table_kb) / 1024 AS AllocatedMB 
FROM sys.dm_db_xtp_table_memory_stats 
GROUP BY object_id 
ORDER BY AllocatedMB DESC; 
 
-- Aktuelle RESOURCE_SEMAPHORE-Wartenden 
SELECT  
session_id, 
requested_memory_kb, 
granted_memory_kb, 
required_memory_kb 
FROM sys.dm_exec_query_memory_grants 
WHERE grant_time IS NULL;
📈 Zielwerte: Die Summe der TempDB-Allokationen sollte nach der Umstellung auf speicheroptimierte Tabellenvariablen deutlich sinken. RESOURCE_SEMAPHORE sollte idealerweise nahe 0 ms durchschnittlicher Wartezeit haben.

✅ Best Practices für speicheroptimierte TempDB-Entlastung und RESOURCE_SEMAPHORE-Vermeidung

PRAXIS
  • Speicheroptimierte Tabellenvariablen bevorzugen: Ersetzen Sie kleine temporäre Tabellen (#temp) durch DECLARE @t TABLE ... WITH (MEMORY_OPTIMIZED = ON).
  • TempDB klassisch optimieren: Auch wenn Sie In-Memory nutzen, bleibt die TempDB für andere Objekte relevant. Mehrere Dateien, gleiche Größe, SSD.
  • RESOURCE_SEMAPHORE überwachen: Richten Sie Alerts ein, wenn die durchschnittliche Wartezeit 100 ms überschreitet.
  • max server memory und MAXDOP justieren: Nicht zu niedrig, nicht zu hoch. Nutzen Sie die Empfehlungen von Tools wie dem SQL Server Health Check.
  • Abfragen mit großen Speicheranforderungen identifizieren: Nutzen Sie die DMVs aus diesem Artikel regelmäßig.
  • Auf SQL Server 2016 oder neuer aktualisieren: Nur dann sind speicheroptimierte Tabellenvariablen verfügbar.
  • Speicherdruck durch andere Anwendungen vermeiden: Lassen Sie dem SQL Server ausreichend RAM, reservieren Sie feste Grenzen in Virtualisierungen.

⚠️ Häufige Irrtümer zu Memory-Optimized TempDB und RESOURCE_SEMAPHORE

AUFRÄUMEN
  • ❌ „Es gibt eine Memory-Optimized TempDB, ich muss nur einen Schalter umlegen.“ → Falsch. Die TempDB kann nicht speicheroptimiert werden. Aber In-Memory OLTP kann die TempDB entlasten.
  • ❌ „RESOURCE_SEMAPHORE bedeutet immer zu wenig RAM.“ → Nicht immer. Oft sind schlechte Abfragen oder falsche Parallelitätseinstellungen die Ursache.
  • ❌ „Speicheroptimierte Tabellenvariablen sind genauso langsam wie normale.“ → Falsch. Sie sind oft 5-20x schneller, weil sie keine Latchs benötigen und nicht in die TempDB schreiben.
  • ❌ „Ich kann jede temporäre Tabelle einfach umstellen.“ → Nicht ganz. Achten Sie auf die Einschränkungen (keine IDENTITY CACHE, bestimmte Datentypen, keine ALTER nach Erstellung).
  • ❌ „Nach DBCC DROPCLEANBUFFERS ist RESOURCE_SEMAPHORE gelöst.“ → Nein, das verschlimmert das Problem sogar, weil jetzt noch mehr Abfragen von der Platte lesen müssen und Speicher für neue Seiten anfordern.
🚀 Fazit: Die Kombination aus In-Memory OLTP (speziell speicheroptimierten Tabellenvariablen) und einer sauberen Speicherkonfiguration kann sowohl TempDB-Probleme als auch RESOURCE_SEMAPHORE-Wartezeiten drastisch reduzieren. Beginnen Sie mit einer Analyse Ihrer zeitraubendsten Abfragen und ersetzen Sie nach und nach kritische temporäre Tabellen durch speicheroptimierte Alternativen. Überwachen Sie gleichzeitig die Speicherzuteilungen, um Engpässe früh zu erkennen.