Ich erkläre, wie das mysql query cache behavior in modernen Hosting-Umgebungen wirkt, warum MySQL 8.0 den internen Query Cache abgeschafft hat und wie ich mit Redis oder Memcached spürbar schneller werde. Ich zeige klare Stellschrauben für Query-Caching, Cache-Invalidierung, Monitoring und Hardware, mit denen Websites öfter aus dem Cache liefern und Datenbanken weniger arbeiten.
Zentrale Punkte
- MySQL 8.0: Interner Query Cache weg, externe Caches übernehmen.
- In-Memory: Häufig gelesene Daten blitzschnell aus RAM.
- Invalidierung: TTL, Events und Versionierung gegen veraltete Daten.
- Monitoring: Hit-Ratio, Latenz und Evictions steuern Tuning.
- 300%: Richtiges Caching reduziert Last und hebt Performance.
Query-Cache-Verhalten im Hosting kurz erklärt
Wenn Anfragen eintreffen, prüfe ich zuerst, ob das Ergebnis bereits im Cache liegt. Liegt es dort, antworte ich ohne Datenbankzugriff und spare Latenz sowie CPU-Zeit auf dem Datenbankserver. Fehlt der Eintrag, erstelle ich das Ergebnis, speichere es im Cache und liefere es aus, damit der nächste Treffer schneller ist und die Seitenladezeit sinkt. So reduziere ich die Anzahl identischer Abfragen und verringere die Serverlast bei wiederkehrenden Zugriffen auf beliebte Inhalte. In Hosting-Setups mit vielen gleichartigen Requests (Startseite, Produktlisten, Menüstrukturen) bringt das Query-Cache-Verhalten deutliche Beschleunigung.
Von MySQL Query Cache zu Redis/Memcached: zeitgemäßer Weg
Der alte MySQL-Query-Cache bremste bei vielen Schreibzugriffen, deshalb entfernte MySQL 8.0 die Funktion. Ich setze stattdessen auf Redis oder Memcached, weil ich damit Caches unabhängig von der Datenbank steuere und granulare Schlüssel, TTLs und Eviction-Strategien nutzen kann. Dadurch entlaste ich MySQL spürbar, denn Leseanfragen treffen häufiger den In-Memory-Cache, während MySQL sich auf echte Transaktionen konzentriert. Ich halte Cache-Keys bewusst klein, versionsiere sie bei Änderungen und sichere so eine hohe Trefferquote. Dieser Ansatz liefert konsistente Antworten bei hoher Auslastung und skaliert quer über mehrere Worker oder Container.
Warum wurde der interne Query Cache wirklich entfernt? Er blockierte stark parallelisierte Systeme durch globale Locks, invalidierte bei Änderungen oft komplette Tabellenbereiche und verursachte viel Verwaltungs-Overhead bei gemischten Lese-/Schreib-Workloads. Das Ergebnis: je mehr Schreibzugriffe, desto niedriger der Nutzen – bis hin zur Netzbremse. Moderne Caches liegen deshalb außerhalb von MySQL, nutzen pro Key isolierte TTLs, erlauben horizontales Skalieren und lassen sich unabhängig deployen. MySQL selbst profitiert weiter über den InnoDB-Buffer-Pool, gute Indexe und vorbereitete Statements – Ergebnis-Caching bleibt aber Aufgabe der Anwendungsebene.
Cache-Ebenen verständlich: In-Memory, Datenbank, Anwendung
Ich unterscheide drei Ebenen, damit das Caching sauber greift: anwendungsnaher Cache (Redis/Memcached), datenbanknaher Cache (z. B. Buffer Pool) und HTTP/Reverse-Proxy-Caches. Anwendungsnah cachte ich komplette Query-Ergebnisse oder gerenderte Fragmente, was die höchste Flexibilität bietet. Datenbanknah profitiere ich von optimierten Indexen und dem InnoDB Buffer Pool, der häufig gelesene Seiten im RAM hält. Auf HTTP-Ebene minimiere ich dynamische Aufrufe, wenn Inhalte wirklich statisch sind. Einen schnellen Überblick zu Taktiken biete ich im kompakten Leitfaden zu Caching-Strategien, der den passenden Einsatz je nach Anwendungsszenario erleichtert.
Caching-Patterns im Vergleich
Ich wähle das Muster passend zum Risiko, zur Änderungsfrequenz und zum Konsistenzbedarf:
- Cache-Aside (Lazy Loading): Anwendung prüft Cache, lädt aus DB bei Miss, schreibt ins Cache. Einfach, flexibel, geringe Kopplung – aber anfällig für Stampedes bei Ablauf der TTL.
- Read-Through: Eine Cache-Schicht lädt automatisch aus der Datenquelle. Einheitliches Verhalten, aber zusätzliche Komplexität in der Zwischenschicht.
- Write-Through: Bei jedem Write wandern Daten erst in den Cache und dann in die DB. Sehr konsistent, aber Schreibpfad wird länger.
- Write-Behind: Cache nimmt Schreibvorgänge entgegen und flusht asynchron in die DB. Schnell, aber bei Ausfall heikel; nur mit klaren Garantien einsetzen.
- Stale-While-Revalidate: Abgelaufene Einträge dürfen kurz „alt“ zurückgegeben werden, während ein Hintergrundjob frisch befüllt. Ideal gegen Lastspitzen.
Cache-Invalidierung ohne Datenfehler
Cache-Invalidierung plane ich so, dass aktuelle Daten stets Vorrang haben und Schnelligkeit bleibt. Ich setze Time-to-Live (TTL) kurz genug, um Änderungen zeitnah zu zeigen, aber lang genug, damit die Hit-Ratio hoch bleibt. Bei Schreibvorgängen lösche ich gezielte Keys (Write-Through/Write-Behind) oder erhöhe eine Version im Key-Namespace, damit Folgezugriffe den frischen Datensatz ziehen. Für sensible Inhalte (Preise, Bestände, Konten) nutze ich kürzere TTL oder sofortige Invalidierung nach Updates. So verhindere ich veraltete Antworten und halte Datenkonsistenz in verteilten Systemen.
Cache-Stampede verhindern: Stale-While-Revalidate, Locks und Jitter
Um das „Dogpile-Problem“ zu vermeiden, setze ich kombinierte Mechanismen ein: eine Soft-TTL, die einige Sekunden „stale“ erlaubt, während ein Single-Flight-Worker das Objekt aktualisiert; ein kurzer Mutex (z. B. via Redis SET NX + TTL), damit nur ein Prozess nachlädt; und ein Jitter auf TTLs (zufällige Abweichung), damit nicht tausende Keys zeitgleich auslaufen. Bei Fehlern zur Ursprungsquelle erlaube ich stale-if-error und schütze die Datenbank vor Lawinen.
Größe, TTL und Eviction: die richtigen Stellschrauben
Ich wähle die Cache-Größe passend zum Datenvolumen, das sich lohnt, im RAM zu liegen. Zu klein erhöht Misses, zu groß verschwendet Speicher, deshalb messe ich kontinuierlich und reagiere auf Lastspitzen. Bei Eviction setze ich bevorzugt LRU, wenn Zugriffsmuster zyklisch sind, und wechsle zu LFU bei klaren Dauerbrennern. Ich halte TTLs differenziert: statische Navigation länger, dynamische Produktverfügbarkeiten kürzer. Die folgende Tabelle zeigt typische Startwerte, die ich anschließend per Monitoring nachschärfe und an reale Nutzung anpasse.
| Parameter | Zweck | Startwert | Messgröße |
|---|---|---|---|
| Cache-Größe | RAM-Budget für Query- oder Fragment-Caches | 5–15% des Server-RAM | Evictions/Minute, RAM-Auslastung |
| TTL statisch | Menüs, Kategorieseiten, häufige Listings | 300–1800 Sekunden | Hit-Ratio, Aktualitätsbedarf |
| TTL dynamisch | Preise, Lager, Personalisierung | 10–120 Sekunden | Fehlerquote, Korrekturen |
| Eviction | LRU/LFU/FIFO je Zugriffsmuster | LRU als Standard | Miss-Rate, Wiederholzugriffe |
| Key-Schema | Versionierung gegen veraltete Daten | user:v1:queryhash | Fehltreffer nach Deploy |
Ich berücksichtige zudem Objektgrößen-Verteilungen und Obergrenzen. Einzelobjekte über z. B. 512 KB komprimiere ich oder teile sie in Seiten (paging), damit Evictions nicht ganze Megabyte-Blöcke verdrängen. Unterschiedliche Caches (z. B. „hot“ und „cold“) mit getrennten Größen verhindern, dass wenige Großobjekte die vielen kleinen, häufig gelesenen Einträge verdrängen.
Key-Design und Normalisierung
Gute Schlüssel entscheiden über Trefferquote und Invalidierbarkeit. Ich normalisiere Query-Parameter (Sortierung, Groß-/Kleinschreibung, Standardwerte), wandle Listen in eine kanonische Reihenfolge und hashe lange Parameter in einen Query-Hash, damit Keys kurz bleiben. Im Schlüssel trenne ich Facetten sauber: site:v3:de-DE:category:42:page:2:filter:abc123. Personalisierung, Mandant, Währung, Locale und Gerätekategorie gehören sichtbar in den Namespace. Numerische Parameter quantisiere ich (z. B. Preisfilter runde ich auf sinnvolle Buckets), um Duplikate zu vermeiden. Negative Caches (z. B. „kein Treffer“) mit sehr kurzer TTL senken DB-Zugriffe bei wiederholten Miss-Suchen.
Serialisierung und Komprimierung richtig wählen
Ich wähle Formate nach Schnittstelle und CPU-Budget: JSON ist universell und lesbar, MessagePack oder Protobuf sparen RAM/Bandbreite. Bei großen Objekten verwende ich LZ4 oder Snappy für schnelle Kompression; Gzip nur, wenn maximale Größe wichtiger ist als CPU. Eine Schwelle (z. B. ab 4–8 KB) verhindert, dass Kleindaten unnötig komprimiert werden. Ich achte auf stabile Schemas: Füge ich Felder hinzu, erhöhe ich die Key-Version, damit alte Parser nicht brechen.
Redis vs. Memcached: Unterschiede im Betrieb
Memcached punktet mit einfacher Architektur, Multithreading und Slabs für effiziente Allokation. Es ist die erste Wahl für sehr einfache Key/Value-Ergebnisse mit extrem hohen QPS ohne Persistenzbedarf. Redis bietet Datenstrukturen (Hashes, Sets, Sorted Sets), feine TTL-Steuerung, Replikation und Cluster-Fähigkeit. Für Listen, Leaderboards, Zähler und Pub/Sub ist Redis ideal. Als reiner Ergebnis-Cache deaktiviere ich Persistenz (oder setze sparsame Snapshots), um I/O zu sparen. Ich nutze Pipeline und MGET, um Roundtrips zu reduzieren, und wähle die Eviction-Policy passend zum Zugriffsmuster (allkeys-lfu bei klaren Dauerbrennern, volatile-lru bei strikter TTL-Nutzung). Hot-Keys verteile ich über Sharding/Cluster, oder ich repliziere sie bewusst mehrfach, um Engpässe abzufedern.
Monitoring und Tuning im Betrieb
Ich beobachte die Hit-Ratio, die Latenz pro Cache-Operation und die Eviction-Rate, um Engpässe zu erkennen. Steigt die Latenz, prüfe ich Netzwerkpfade, CPU-Sättigung und die Serialisierung von Objekten. Ich senke große Objekte durch Komprimierung oder teile sie in kleinere, um Speicher besser zu nutzen. Bei sinkender Hit-Ratio identifiziere ich fehlende Keys und passe TTLs oder Key-Schemata an. Tuning bleibt ein Kreislauf aus Messen, Hypothesen, Anpassung und erneuter Messung.
Konkrete Kennzahlen helfen bei der Ursachenanalyse: keyspace_hits/-misses, evicted_keys, reclaimed (Memcached), used_memory und RSS-Abweichungen für Fragmentierung, P99-Latenzen pro Befehl, Netzwerk-Fehlerraten und Slowlog-Einträge. Ich achte auf kontinuierliche, nicht sprunghafte Evictions, gleichmäßig verteilte Objektgrößen und den Anteil an „stale served“. Wenn miss→db→set häufiger als geplant abläuft, stimmt entweder die TTL nicht oder die Keys werden zu breit variiert (fehlende Normalisierung).
Sicherheit und Hochverfügbarkeit
Ich exponiere Cache-Server niemals öffentlich, sondern binde sie an interne Interfaces/VPCs, aktiviere ACLs und wo möglich TLS. Produktions-, Staging- und Test-Umgebungen trenne ich strikt, damit keine Schlüssel kollidieren und keine Daten wandern. Kritische Operationen (FLUSH*) sperre ich über Berechtigungen. Für Failover setze ich Replikation und je nach Technologie automatisches Umschalten (z. B. Watchdog/Sentinel/Cluster). Als reiner Ergebnis-Cache kommt Persistenz nur sparsam oder gar nicht zum Einsatz – fällt der Cache aus, darf die Anwendung lediglich langsamer, aber korrekt sein. Ich begrenze Kommandos, die ganze Keyspaces scannen, und plane Backups nur dort, wo der Cache zugleich Source of Truth ist (selten der Fall).
WordPress und E-Commerce: typische Muster und Fallen
Bei WordPress cache ich Menüstrukturen, Query-Ergebnisse von WP_Query und wichtige Widgets, während ich personalisierte Teile ausnehme. Ich achte darauf, dass Plugins nicht jeden Request umgehen, indem sie Sessions setzen oder ständig Cookies verändern. Für Shop-Systeme cache ich Kategorieseiten, Bestseller-Listen und Filter-Resultate mit kurzer TTL, während Warenkörbe und Kontoseiten dynamisch bleiben. Wer auf den alten Query-Cache setzt, verschlechtert oft die Performance; warum das so ist, erkläre ich hier: WordPress Query Cache. So halte ich die Balance zwischen Geschwindigkeit und korrekter Personalisierung.
Zusätzlich variiere ich Caches an den richtigen Stellen: Währung, Sprache, Standort und Kundengruppe beeinflussen Preise, Verfügbarkeiten und Inhalte. Ich entkopple Personalisierung vom Rest: Die Seite kommt aus dem Cache, nur kleine Blöcke (z. B. Warenkorb-Count) werden dynamisch nachgeladen. Bei stark veränderlichen Filtern (Facetten) normalisiere ich die Reihenfolge und baue Seiten-Keys (page=1,2,…), statt riesige, unübersichtliche Schlüssel zu erzeugen. Und ich stelle sicher, dass „Kein Ergebnis“-Antworten kurzzeitig gecacht werden, um DB-Scans zu reduzieren.
Kapazitätsplanung und Kostenmodell
Ich rechne vorab grob: Durchschnittliche Objektgröße × erwartete Anzahl Keys + Overhead (10–30%) ergibt die RAM-Basis. Beispiel: 80.000 Objekte à 6 KB plus 25% Overhead ≈ 600 MB. Für Wachstum plane ich Puffer (z. B. 30–50%). Auf der Durchsatzseite schätze ich Lese-/Schreibverhältnis, Ziel-Hit-Ratio (70–95%) und die resultierende Entlastung der Datenbank. Wenn 60% der bisherigen DB-Reads aus dem Cache bedient werden, sinken nicht nur CPU- und IOPS-Last, sondern oft auch die Replikations-Lags. Ich bepreise Szenarien: RAM verteuern, DB-Kerne sparen – meist gewinnt die RAM-Investition deutlich, weil sie gleichmäßigere Antwortzeiten bringt.
InnoDB Buffer Pool, Query Plan und Indexe zusammen denken
Ich optimiere nicht isoliert, sondern betrachte Cache, Buffer Pool, Abfrageplan und Indexe als Paket. Ein gut dimensionierter Buffer Pool erhöht InnoDB-Hits, reduziert I/O und stärkt jeden Cache darüber. Ich prüfe langsame Abfragen, lege fehlende Indexe an und halte Statistiken frisch, damit der Optimizer den besten Plan wählt. Für tiefergehende Schritte hilft diese Buffer-Pool-Optimierung, die ich parallel zum Caching einsetze. So addiert sich Geschwindigkeit: weniger I/O, mehr RAM-Treffer und effizientere Abfragen.
Praktisch heißt das: Den Buffer Pool dimensioniere ich so, dass „heiße“ Datenseiten hineinpassen, ohne das Betriebssystem auszuhungern. Query-Profile decken auf, ob Full-Table-Scans, suboptimale JOINs oder fehlende Covering-Indexe Caches unterlaufen. Ich prüfe, ob zu breite SELECTs (unnötige Spalten) große Cache-Objekte erzeugen, und verschlanke sie. Wenn Abfragen stark variieren, normalisiere ich Parameter in der Anwendung oder führe sie auf wenige, wiederverwendbare Varianten zurück.
Hardware-Ressourcen richtig einsetzen
Ich reserviere genug RAM für Redis/Memcached und für den InnoDB Buffer Pool, damit Festplatten kaum blockieren. Ich achte auf CPU-Kerne, damit Applikation und Cache-Server gleichzeitig arbeiten können. NVMe-SSDs mindern die Restlatenz, wenn ein Cache-Miss doch zum Speicher greift. Netzwerk-Latenz bleibt wichtig, deshalb setze ich Cache-Server nahe an die App oder im selben Host. Diese Entscheidungen sparen oft Hosting-Kosten in Euro, weil ich mit weniger Kernen und niedrigerer Last gleiche Antwortzeiten erreiche.
Zusätzlich beachte ich NUMA- und Socket-Topologien, pinne Prozesse bei Bedarf auf Kerne und nutze kurze Netzwerkpfade (oder Unix-Sockets auf demselben Host). Für Container-Setups plane ich „Guaranteed“-Ressourcen, damit der Cache nicht gedrosselt wird, und halte Headroom für Spitzenlasten vor. Wenn Hot-Keys unvermeidbar sind, verteile ich Traffic über mehrere Replikate oder route ich an den lokalsten Cache, um Cross-Zone-Latenzen zu vermeiden.
Rollout, Tests und Cache-Warmup
Ich teste Caching-Änderungen mit Lastprofilen, die echte Nutzungsdaten widerspiegeln. In der Produktion rolle ich in Stufen aus (Canary), beobachte Hit-Ratio, Latenzen und DB-Last und erhöhe erst dann die TTLs. Bei Deployments erhöhe ich die Key-Version und wärme die Top-N-Keys vor (Homepage, Topseller, wichtige Kategorien). Hintergrundjobs füllen Listen- und Detailseiten gezielt, damit die ersten Nutzer nicht die Aufwärmkosten tragen. Ich simuliere Evictions (Test-Umgebung) und stresse Hot-Paths, um Stampede-Schutz und Jitter zu verifizieren.
Schritt-für-Schritt-Plan für bessere Hosting-Performance
Ich starte mit einer Bestandsaufnahme: langsame Abfragen, Logfiles, Hit-Ratio, Evictions und CPU-/RAM-Profile. Danach definiere ich Cache-Keys für die wichtigsten Seiten und lege TTLs fest, die Aktualität und Geschwindigkeit ausbalancieren. Ich baue Write-Through oder Event-basierte Invalidierung für Änderungen ein, damit Konsistenz bleibt. Anschließend messe ich erneut, erhöhe oder senke TTLs, passe die Cache-Größe an und entferne Ausreißer mit großen Objekten. Zum Schluss schärfe ich den Buffer Pool, Indexe und Pläne nach, bis die Seitenauslieferung spürbar flüssig läuft.
Kurz zusammengefasst
Ich ersetze den alten MySQL-Query-Cache durch Redis oder Memcached, steuere Schlüssel, TTLs und Evictions bewusst und halte Daten mit klarer Invalidierung verlässlich. So erreiche ich je nach Anwendung 200–300% Geschwindigkeit, vor allem, wenn viele identische Anfragen eintreffen. Monitoring lenkt meine Entscheidungen: Sinkt die Hit-Ratio oder steigt die Latenz, passe ich Größe, TTL und Schlüssel an. Zusammen mit einem starken InnoDB Buffer Pool und sauberen Indexen skaliert die Plattform besser und reagiert sehr schnell. Wer das mysql query cache behavior als Gesamtsystem versteht, spart Serverlast, senkt Kosten in Euro und liefert Nutzern eine knackige User-Experience.


