Optimierung von MySQL-Abfragen in Pawn: Server-Freezes erfolgreich beheben

Ein technischer Leitfaden zur Konvertierung blockierender synchroner Abfragen in asynchrone Hintergrund-Threads mit BlueG MySQL in SA:MP/CRMP-Skripten.

20.05.2026 Deutsch

Optimierung von MySQL-Abfragen in Pawn: Server-Freezes erfolgreich beheben

Die Server-Engine von SA:MP/CRMP arbeitet nach einem strikten einzelthreadbasierten Prinzip (Single-Threaded). Das bedeutet, dass absolut alle Prozesse—Spielerbewegungen, Timer-Schleifen, Befehlsverarbeitungen, Anticheat-Prüfungen und Dateizugriffe—nacheinander in einem einzigen Hauptthread ausgeführt werden. Wenn eine Funktion in Ihrem Pawn-Skript 500 Millisekunden zur Ausführung benötigt, friert der gesamte Server buchstäblich für eine halbe Sekunde ein. Für die Spieler äußert sich dies in plötzlichen Ping-Spikes, verzögerten Animationen oder kurzen Rucklern.

Der häufigste Verursacher solcher Mikroruckler und Freezes ist eine inkorrekte Interaktion mit der MySQL-Datenbank. In diesem Artikel analysieren wir den Unterschied zwischen den Abfragetypen im BlueG MySQL-Plugin und zeigen, wie eine einzige unoptimierte Abfrage während der Autorisierung den gesamten Server lahmlegen kann.

Blockierende Abfragen (mysql_query) — Der größte Fehler des Entwicklers

In älteren Versionen des MySQL-Plugins (und leider auch in vielen veralteten Skripten) wird die Funktion mysql_query() zum Senden von SQL-Befehlen verwendet. Dies ist eine sogenannte synchrone (blockierende) Abfrage.

Wenn der Server ein mysql_query ausführt, passiert Folgendes:

  1. Der Hauptthread des Servers stoppt die Verarbeitung des Spielgeschehens.
  2. Der Server sendet die Abfrage über das Netzwerk an den MySQL-Server des Hostings.
  3. Der SA:MP-Server wartet, bis MySQL den passenden Eintrag in der Tabelle findet, verarbeitet und die Antwort zurücksendet.
  4. Erst nach Erhalt der Antwort wacht der Hauptthread wieder auf und führt das Spiel fort.

Ein katastrophales Beispiel: Ein Spieler betritt den Server mit dem Namen Ivan_Petrov. Das Skript sendet eine synchrone Abfrage: mysql_query(db_handle, "SELECT * FROM users WHERE name = 'Ivan_Petrov'");. Wenn die Account-Tabelle mittlerweile 50.000 Datensätze enthält und die Spalte name keinen Index besitzt, benötigt die Datenbank etwa 200–400 Millisekunden, um die gesamte Festplatte zu lesen. Während dieser Zeit ist der Server für die anderen 200 aktiven Spieler komplett blockiert.

Asynchrone Abfragen (mysql_tquery und mysql_pquery) — Die Rettung für die Server-TPS

Moderne Versionen des BlueG MySQL-Plugins (R39-R41+) bieten ein mächtiges Werkzeug: Asynchronität. Die Funktionen mysql_tquery (Threaded Query) und mysql_pquery (Parallel Query) verarbeiten die Last in separaten, parallelen Threads des Linux-Betriebssystems, ohne die eigentliche Spiel-Engine zu beeinträchtigen.

Bei der Verwendung des asynchronen Ansatzes ändert sich der Ablauf grundlegend:

  1. Der Hauptthread ruft mysql_tquery auf, übergibt die Aufgabe sofort an den parallelen Thread des Plugins und setzt das Spielgeschehen direkt fort (Spieler bewegen sich flüssig, Treffer registrieren ordnungsgemäß, die TPS bleiben stabil bei 50).
  2. Der parallele Thread kommuniziert im Hintergrund mit der Datenbank, wartet auf die Antwort und verarbeitet die schwere SQL-Abfrage.
  3. Sobald die Daten bereitstehen, signalisiert das MySQL-Plugin dem Hauptthread das Ergebnis und ruft eine spezielle Antwortfunktion auf — den Callback, in dem wir dem Spieler sicher sein Level und sein Geld zuweisen können.

Praktisches Beispiel: Umstellung der Autorisierung auf asynchrone Abläufe

Wie man es NICHT machen sollte (Synchroner, blockierender Code):

public OnPlayerConnect(playerid)
{
    new query[128], Cache:result;
    GetPlayerName(playerid, query, MAX_PLAYER_NAME);
    format(query, sizeof(query), "SELECT id FROM users WHERE name = '%s'", query);
    
    result = mysql_query(db_handle, query);
    
    if(cache_num_rows() > 0) {
        ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Login", "Passwort eingeben:", "Einloggen", "Beenden");
    } else {
        ShowPlayerDialog(playerid, DIALOG_REG, DIALOG_STYLE_INPUT, "Registrierung", "Passwort ausdenken:", "Weiter", "Beenden");
    }
    cache_delete(result);
    return 1;
}

Der RICHTIGE Ansatz (Asynchroner Code mit Callback):

public OnPlayerConnect(playerid)
{
    new query[128], p_name[MAX_PLAYER_NAME];
    GetPlayerName(playerid, p_name, sizeof(p_name));
    
    format(query, sizeof(query), "SELECT id FROM users WHERE name = '%s'", p_name);
    
    mysql_tquery(db_handle, query, "CheckPlayerAccount", "d", playerid);
    return 1;
}

forward CheckPlayerAccount(playerid);
public CheckPlayerAccount(playerid)
{
    if(!IsPlayerConnected(playerid)) return 1;
    
    if(cache_num_rows() > 0) {
        ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Login", "Passwort eingeben:", "Einloggen", "Beenden");
    } else {
        ShowPlayerDialog(playerid, DIALOG_REG, DIALOG_STYLE_INPUT, "Registrierung", "Passwort ausdenken:", "Weiter", "Beenden");
    }
    return 1;
}

Was ist der Unterschied zwischen mysql_tquery und mysql_pquery?

  • mysql_tquery: Erstellt einen einzelnen Hintergrund-Thread für Abfragen. Alle asynchronen Abfragen werden in einer einzigen Hintergrundwarteschlange abgelegt und strikt nacheinander ausgeführt. Dies garantiert, dass Abfragen in der exakten chronologischen Reihenfolge verarbeitet werden – wichtig z. B. wenn Sie Daten erst speichern und direkt danach wieder laden möchten. Ideal für 95 % aller Aufgaben.
  • mysql_pquery: Erstellt einen Pool aus mehreren parallelen Threads. Die Abfragen werden gleichzeitig auf verschiedenen CPU-Kernen verarbeitet. Dies beschleunigt die Abfragezeiten, jedoch ist die Verarbeitungsreihenfolge nicht garantiert. Nutzen Sie mysql_pquery daher nur für rechenintensive Protokollierungen oder Log-Einträge (z. B. Kill-Logs oder Admin-Aktionen), bei denen die chronologische Reihenfolge für die Account-Logik unkritisch ist.

Die goldene Regel der Datenbank-Indizierung

Selbst eine asynchrone Abfrage kann Lags verursachen, wenn die Datenbank unvorteilhaft konfiguriert ist. Wenn ein Hintergrund-Thread 5 Sekunden für eine SQL-Abfrage benötigt, hängt der betroffene Spieler ewig am Spawnpunkt fest und wartet auf das Dialogfenster.

Um dies zu verhindern, fügen Sie unbedingt Indizes (INDEX / UNIQUE) für alle Felder hinzu, nach denen Sie filtern (WHERE). In der User-Tabelle müssen zwingend Indizes auf den Spalten name (Benutzername) und id liegen.

Befehl zur Optimierung via phpMyAdmin:
Öffnen Sie die SQL-Registerkarte Ihrer Datenbank im Hosting-Panel und führen Sie folgenden Befehl aus:
ALTER TABLE `users` ADD INDEX (`name`);
Dies beschleunigt die Account-Suche beim Verbindungsaufbau um das Hundertfache und senkt die CPU-Last Ihrer Server-Hardware auf ein Minimum.

Ähnliche Artikel

Schutz von SA:MP-Servern gegen Flood-Angriffe und gefälschte Pakete in Pawn

Ein technischer Leitfaden zur Absicherung von SA:MP-Servern vor Paket-Flooding, Dialog-Spam und unzulässigem Fahrzeug-Spawning mittels Pawn.RakNet und Nex-AC.

Weiterlesen

Unsichtbare Spieler Bug und Desynchronisation virtueller Welten beheben

Eine technische Analyse des Unsichtbare-Spieler-Glitches sowie der Desynchronisation virtueller Welten in SA:MP/CRMP-Servern inklusive eines sicheren Teleportations-Algorithmus.

Weiterlesen

Bekämpfung von Speicherlecks (Memory Leaks) in AMX-Skripten

Ein technischer Leitfaden zur Erkennung und Behebung von Speicherlecks in SA:MP/CRMP-Server-Skripten. Behandelt MySQL-Cache-Freigabe, Streamer-Entitäten und Heap-Bereinigungen.

Weiterlesen