GH GambleHub

Indexierung und Optimierung von Abfragen

1) Indexierungs- und Optimierungsziele

Latenz: Reduzierung der P50/P95/P99.
Bandbreite: QPS-Wachstum ohne horizontale Skalierung.
Vorhersagbarkeit: stabile Pläne und keine „Sprünge“ in der Reaktionszeit.
Einsparungen: weniger IO/CPU, weniger Cloud-Rechnung.
Zuverlässigkeit: Reduzierung von Schlössern und Dedlocks durch korrekte Zugriffe.

Invarianten:
  • Jede Optimierung muss korrekt und konsistent bleiben.
  • Verfolgen Sie den Effekt in Metriken und Planprotokollen.

2) Grundlegende Indexstrukturen und wann sie anzuwenden sind

2. 1 B-Baum (Ausfall)

Gleich/Bereiche, Sortierung, 'ORDER BY'.
Gut für die meisten Zeit-/ID/Status-Filter.

2. 2 Hash

Reine Gleichheit ('='), billiger im Speicher, aber ohne Ordnung (PG: Begrenzungen entfernt, aber immer noch eine Nischenwahl).

2. 3 GIN / GiST (PostgreSQL)

GIN: Arrays/JSONB-Schlüssel, Volltext (tsvector), Inhalt ('@>').
GiST: Geo, Bereiche, kNN.

2. 4 BRIN (PostgreSQL)

Super-günstiger Index nach „natürlich sortierten“ Tischen (nur append nach Zeit). Gut für Zeitreihen mit großen Tabellen.

2. 5 Bitmap (MySQL/InnoDB: nicht nativ; DW-DBMS/OLAP)

Wirksam für niedrige Kardinalität und Facetten, häufiger in Säulenlagern.

2. 6 Säulenindizes (ClickHouse)

Primary key + data skipping (minmax), secondary через `skip indexes` (bloom, set).
OLAP-Abfragen mit Aggregationen und Bereichen.

2. 7 Invertierte Indizes (Elasticsearch/OpenSearch)

Volltext, Facetten, hybride Suche. Verwenden Sie Keyword-Felder und Doc-Werte für genaue Filter.

2. 8 MongoDB

Single, Compound, Multikey (Arrays), Partial, TTL, Text, Hashed (für Sharding mit einem einheitlichen Schlüssel).

3) Entwurf von Schlüsseln und zusammengesetzten Indizes

3. 1 Regel „linkes Präfix“

Die Reihenfolge der Felder im Index bestimmt die Verwendbarkeit.
Die Abfrage' WHERE tenant_id =? AND created_at >=? ORDER BY created_at DESC` → индекс `(tenant_id, created_at DESC, id DESC)`.

3. 2 Tie-breaker

Fügen Sie einen einzigartigen Schwanz (normalerweise' id') für eine stabile Sortierung und Seek-Pagination hinzu.

3. 3 Partielle/gefilterte Indizes

Indizieren Sie nur die „heißen“ Teilmengen:
sql
CREATE INDEX idx_orders_paid_recent
ON orders (created_at DESC, id DESC)
WHERE status = 'paid' AND created_at > now() - interval '90 days';

3. 4 Deckungsindizes

Fügen Sie „lesbare“ Felder in den Index ein (MySQL: 'INCLUDE' nein; PG 11+: `INCLUDE`):
sql
CREATE INDEX idx_user_lastseen_inc ON users (tenant_id, last_seen DESC) INCLUDE (email, plan);

3. 5 Funktional/berechnet

Normalisieren Sie die Schlüssel im Index:
sql
CREATE INDEX idx_norm_email ON users (lower(email));

4) Partitionierung und Sharding

4. 1 Partitionierung (PG native/tabellarische Vererbung; MySQL RANGE/LIST)

Die Rotation der Parteien nach Zeit („täglich/wöchentlich“) vereinfacht „VACUUM/DELETE“.
Indizes sind lokale Parteien → weniger B-Tree, schneller Plan.

sql
CREATE TABLE events (
tenant_id bigint,
ts timestamptz,
...
) PARTITION BY RANGE (ts);

4. 2 Partitionierungsschlüssel

In OLTP durch 'tenant _ id' (Lastlokalisierung).
In der Zeitreihe/OLAP - durch 'ts' (Bereichsanfragen).
Hybrid:'(tenant_id, ts)'+ Unterparteien.

4. 3 Sharding

Consistent hashing/range-shard durch 'tenant _ id' oder durch die Zeit.
Cross-Shard-Abfrage → Scatter-Gather und K-Way-Merge; Halten Sie per-shard cursor.

5) Statistiken, Kardinalität und Pläne

5. 1 Aktuelle Statistiken

Aktivieren Sie Auto-Analyse ('autovacuum/autoanalyze'), erhöhen Sie' default _ statistics _ target 'für' schmutzige' Verteilungen.

5. 2 Erweiterte Statistiken (PG)

Korrelierte Spalten:
sql
CREATE STATISTICS stat_user_country_city (dependencies) ON country, city FROM users;
ANALYZE users;

5. 3 Ausführungsplan

Siehe „EXPLAIN (ANALYZE, BUFFERS, VERBOSE)“; Schlüsselfelder:
  • `Rows`, `Loops`, `Actual time`, `Shared Read/Hit`, `Recheck Cond`.
  • Типы join: Nested Loop, Hash Join, Merge Join.
  • Seq Scan vs Index Scan/Only Scan/Bitmap Heap Scan.

5. 4 Stabilität der Pläne

Parametrisierung (vorbereitete Aussagen) kann auf einem schlechten Plan „kleben“. Verwenden Sie plan cache guardrails (PG: 'plan _ cache _ mode = force_custom_plan' für problematische Abfragen) oder einen „Durchlauf“ von Konstanten.

6) Join- und Sortieroptimierung

6. 1 Strategien

Nested Loop: kleiner externer, schneller Index auf dem internen.
Hash Join: große Sets, genügend Speicher unter dem Hash-Tisch.
Merge Join: sortierte Eingänge, vorteilhaft bei bereits vorhandener Reihenfolge.

6. 2 Indizes unter join

Für „A JOIN B ON B.a_id = A.id“ → der Index auf „B (a_id)“.
Für den Filter nach join ist der Index auf den Filterspalten der internen Tabelle.

6. 3 Sortierung

Vermeiden Sie' ORDER BY 'ohne entsprechenden Index; Sortieren auf großen Sätzen Straße nach Speicher/Festplatte.

7) Abfragen neu schreiben (query rewrite)

Befreien Sie sich von „Schneeflocken“ Unterabfragen; in JOIN erweitern.
Verwenden Sie CTE-inline (standardmäßig PG ≥12 inlines CTE, aber 'MATERIALIZED' kann bei Bedarf das Zwischenergebnis erfassen).
Entfernen Sie' SELECT '→ listen Sie die Felder auf (IO/Netzwerk-Einsparungen).
Übertragen Sie die Berechnungen von 'WHERE' auf die indizierbare Form (vorwegnehmbare Spalten).
Aggregationen: Vorläufige Summentabellen/materialisierte Ansichten mit inkrementeller Aktualisierung.

8) Schlachten, Begrenzen und Paginieren

Batch-insert/update: 500-5000 Packungen statt Stück für Stück.
Seek-Paginierung durch'(sort_key, id) 'anstelle von tiefem' OFFSET'.
Limit des Sets vor dem Sortieren/Join (Push-down 'LIMIT').

9) Caching und Denormalisierung

Query-Cache auf Anwendungsebene (Schlüssel = SQL + bind-vars + Version der Rechte).
Materialisierte Ansichten für schwere Einheiten; Rotationsplan/Refresh.
Denormalisierung: Speichern Sie häufig gelesene berechnete Felder (Preis inklusive Rabatt), aber mit Trigger/Hintergrundaufgabe für die Konsistenz.
Redis als L2 für „heiße“ Schlüssel (mit TTL und Behinderung durch Ereignisse).

10) Besonderheiten der populären Motoren

10. 1 PostgreSQL

Индексы: B-Tree, Hash, GIN/GiST, BRIN, partial, functional, INCLUDE.

Beispiel:
sql
CREATE INDEX idx_orders_tenant_created_desc
ON orders (tenant_id, created_at DESC, id DESC)
INCLUDE (amount, status);
Volltext:
sql
CREATE INDEX idx_docs_fts ON docs USING GIN (to_tsvector('russian', title          ' '          body));

10. 2 MySQL/InnoDB

Zusammengesetzte, die Indizes abdecken (durch die Einbeziehung von Feldern in den Schlüssel), unsichtbare Indizes für Tests:
sql
ALTER TABLE orders ALTER INDEX idx_old INVISIBLE; -- check risk-free plans

Statistik für Histogramme ('ANALYZE TABLE... UPDATE HISTOGRAM` в 8. 0).

10. 3 ClickHouse

Primärschlüssel = Sortierung; 'ORDER BY (tenant_id, ts, id)'.

Skip-Indizes:
sql
CREATE TABLE events (
tenant_id UInt64,
ts DateTime64,
id UInt64,
payload String,
INDEX idx_bloom_payload payload TYPE bloom_filter GRANULARITY 4
) ENGINE = MergeTree()
ORDER BY (tenant_id, ts, id);

10. 4 MongoDB

Composite/Cartoons: Die Reihenfolge ist wichtig, Filter und Sortierung müssen mit dem Index übereinstimmen:
js db. orders. createIndex({ tenant_id: 1, created_at: -1, _id: -1 });
db. orders. createIndex({ status: 1 }, { partialFilterExpression: { archived: { $ne: true } } });

Verwenden Sie' hint () 'für die Diagnose, achten Sie auf' covered query'.

10. 5 Elasticsearch/OpenSearch

Schlüsselwort gegen Feldtext; doc_values für Sortierung/Aggregate.
Segmentierung heap: Aggregationen - heavy; Begrenzen Sie' size' und verwenden Sie' composite' Aggregationen (seitenweise Auswahl).
Schalten Sie Analysatoren nicht ein, wenn ein genauer Vergleich erforderlich ist.

11) Wettbewerb, Sperren und MVCC

Kurze Transaktionen; Vermeiden Sie „lange“ Lesungen unter 'REPEATABLE READ' unnötig.
Indexoperationen nehmen auch Blockaden (Verringerung des Schreibens durch die Eingabe).
Online-Indexierung planen: 'CREATE INDEX CONCURRENTLY' (PG), 'ALGORITHM = INPLACE '/' ONLINE' (MySQL).
Einfügungen in den Schwanz für eine Stunde/ID → „heiße Seiten“ des Index; Verteilen Sie den Schlüssel (UUIDv7/Salz).

12) Beobachtbarkeit und SLO

Metriken:
  • 'db _ query _ latency _ ms' (P50/P95/P99) nach dem Namen der Anfrage.
  • `rows_examined`, `rows_returned`, `buffer_hit_ratio`.
  • `deadlocks`, `lock_wait_ms`, `temp_sort_disk_usage`.
  • Anteil der Pläne mit 'Seq Scan', wo 'Index Scan' erwartet wurde.
  • Regress-Alerts beim Ändern der DBMS-Version/Parameter.
Protokolle/Tracing:
  • Aktivieren Sie Slow Query Log mit einem Schwellenwert (z. B. 200 ms).
  • Korrelation von Anfragen mit Spans (trace_id).
  • Nehmen Sie Pläne für problematische Abfragen auf und speichern Sie sie zur Retrospektive im Objektspeicher.
SLO-Beispiel:
  • P95 von „<= 150 ms“ bei „LIMIT <= 50“ und „heißem“ Tenant.
  • P95 Einträge'<= 200 ms' bei Gefechten bis zu 1000 Zeilen.

13) Sicherheit und Multi-Tenant

Indizes nach Zugriffskontrollfeldern ('tenant _ id', 'owner _ id') sind erforderlich.
Richtlinien (RLS/ABAC) müssen ein Vorfilter sein; Ansonsten plant der Optimierer falsch.
Indizieren Sie empfindliche Felder nicht im Klartext; Verwenden Sie Hashes/Token.

14) Anti-Muster

Deep 'OFFSET' ohne Seek-Cursor-Alternative.
„Ein Index für alles“ - Speicherüberlastung und Write-Path.
'SELECT' in kritischen Pfaden.
Funktionen über der Spalte in 'WHERE' ohne Funktionsindex.
Instabile Pläne wegen alter Statistiken.
Kein 'ORDER BY' beim Warten auf eine stabile Ordnung.
Indizes um der Indizes willen: ROI <0 aufgrund des teuren Schreibens/Supports.

15) Checkliste Umsetzung

1. Top-N-Anfragen nach QPS und Zeit → 3-5 Kandidaten auswählen.
2. Entfernen Sie die Pläne' EXPLAIN ANALYZE', überprüfen Sie die Kardinalität vs sachlich.
3. Indizes entwerfen: Reihenfolge der Felder, INCLUDE/partial/functional.
4. Implementieren Sie Partitionierung für große Tabellen (temporäre/tenante Schlüssel).
5. Abfragen umschreiben: 'SELECT' entfernen, einfache CTEs inlinieren, Set einschränken.
6. Aktivieren Sie Batching und Seek-Pagination.
7. Richten Sie den Cache ein: L1/L2, Behinderung durch Ereignisse.
8. Einführung von Planüberwachung und Slow-Log, Regressalarme.
9. Führen Sie Lasttests mit echter Datenverteilung durch.
10. Aktualisieren Sie die Haydlines für die Entwicklung (ORM-Hints, Indexierung, Limits).

16) Vorher/nachher Beispiele

Vorher:
sql
SELECT FROM orders
WHERE status = 'paid'
ORDER BY created_at DESC
LIMIT 50 OFFSET 5000;
Nach:
sql
-- Индекс: (status, created_at DESC, id DESC) INCLUDE (amount, currency)
SELECT id, amount, currency, created_at
FROM orders
WHERE status = 'paid'
AND (created_at, id) < (:last_ts,:last_id)   -- seek
ORDER BY created_at DESC, id DESC
LIMIT 50;

17) ORM und API-Protokolle

Vermeiden Sie N + 1: gierige Stichproben ('includes', 'JOIN FETCH', 'preload').
Explizite Projektionen von Feldern, paginieren mit dem Cursor.
gRPC/REST: Begrenzen Sie' page _ size', fixieren Sie' sort', verwenden Sie undurchsichtige Token.
Plan-Cache: Parametrierung verwenden; Generieren Sie nicht „eindeutige“ SQLs für jeden Aufruf.

18) Migration und Ausbeutung

Fügen Sie Indizes online hinzu und markieren Sie als INVISIBLE/CONCURRENTLY, testen Sie die Pläne und wechseln Sie dann.
Revisionen der Indizes - regelmäßige sanitäre Reinigung: Duplikate, ungenutzt, „tot“ für alte Fitch.
Der Plan der Rotation der Parteien (Drop der alten) und 'VACUUM/OPTIMIZE' Zeitplan.

19) Zusammenfassung

Abfrageoptimierung ist Systemengineering: die richtigen Schlüssel und Indizes, ordentliche Pläne, durchdachte Partitionierung und Sharding, Disziplin in Abfragen und ORM, Caching und Beobachtbarkeit. Durch die Einhaltung der beschriebenen Muster erhalten Sie ein schnelles, vorhersehbares und kostengünstiges System, das gegen Daten- und Lastwachstum resistent ist.

Contact

Kontakt aufnehmen

Kontaktieren Sie uns bei Fragen oder Support.Wir helfen Ihnen jederzeit gerne!

Integration starten

Email ist erforderlich. Telegram oder WhatsApp – optional.

Ihr Name optional
Email optional
Betreff optional
Nachricht optional
Telegram optional
@
Wenn Sie Telegram angeben – antworten wir zusätzlich dort.
WhatsApp optional
Format: +Ländercode und Nummer (z. B. +49XXXXXXXXX).

Mit dem Klicken des Buttons stimmen Sie der Datenverarbeitung zu.