From mysql-async to oxmysql: Safe Migration & Query P…
Publikum: FiveM-Serverbesitzer, -Skripter und -Betreuer
Ziel: Ersetzen mysql-async mit oxmysql sicher, beschleunigen Sie Abfragen und modernisieren Sie Ihre SQL-Nutzung.
Lesen Sie auch:
- FiveM-Serveroptimierung: Das definitive Playbook für 2025 — https://fivemx.com/fivem-server-optimization/
- Adaptermuster: ESX↔QBCore↔QBOX-Exporte, Ereignisse und Player-Modelle — https://fivemx.com/adapter-patterns/
Kurz zusammengefasst
- Verwenden
oxmysql: vorbereitete Anweisungen, Promise/Await-API, bessere Diagnose, starke Leistung. - Minimale Codeänderungen: tauschen
@param→?(positional) oder:Name(benannte) Parameter; ersetzenMySQL.Async.*Anrufe mitMySQL.*/exports.oxmysql:*. - Führen Sie die SQL-„UP“-Skripte aus unten (Charset/Index-Fixes) und halten Sie die Rollback praktisch.
- Überprüfen Sie mit dem Mikro-Benchmark-Kabelbaum am Ende, um Gewinne auf Ihrer Hardware zu bestätigen.
1) Sicherheitscheckliste vor dem Flug
- Vollständige Sicherung:
mysqldump --single-transaction yourdb > backup.sql. - Staging-Umgebung Spiegelung des Produktionsschemas + Datenteilmenge.
- Artefakt & Abhängigkeiten: Aktueller FXServer-Build, neueste
oxmysql. - Ausfallzeitfenster für Produktwechsel (normalerweise < 5 Minuten).
- Integritätstests bereit:
/Spieler, Anmeldefluss, Wirtschaftsoperationen, Garagenoperationen, Inventaroperationen, Sperrprüfungen.
2) Installieren und Verkabeln oxmysql
2.1 server.cfg
# Beenden Sie die Verwendung von mysql-async default_prio 500 # Stellen Sie mysql-async sicher # ← Auskommentieren oder Entfernen # Starten Sie oxmysql default_prio 50 Stellen Sie oxmysql sicher # Verbindungszeichenfolge, die von oxmysql verwendet wird. Setzen Sie mysql_connection_string auf "mysql://user:pass@127.0.0.1:3306/yourdb?charset=utf8mb4" # Optionale Diagnose Setzen Sie mysql_slow_query_warning auf 200 # Protokollieren Sie Abfragen, die langsamer als 200 ms sind. Setzen Sie mysql_debug auf false # True für ausführliche Protokollierung während der Bereitstellung
Halten
mysql-asyncdeaktiviert, aber während der Staging-Phase in Ihrem Ressourcenordner verfügbar (für schnelles Rollback).
3) API-Zuordnung: mysql‑async → oxmysql
mysql-async (Vermächtnis):
- Asynchron:
MySQL.Async.fetchAll,MySQL.Async.fetchScalar,MySQL.Async.execute - Synchronisieren:
MySQL.Sync.fetchAll,MySQL.Sync.fetchScalar,MySQL.Sync.execute - Parameter:
@paramStiltabellen wie{ ['@identifier']=identifier }
oxmysql (modern):
- Rückrufstil über Export:
exports.oxmysql:Abfrage|Skalar|Einzel|Einfügen|Aktualisieren(SQL, Parameter, CB) - Versprechen/Warten über global:
MySQL.query|scalar|single|insert|update.await(sql, params)und nicht-wartende Rückrufe ohne.erwarten - Parameter: positionell
?über Array oder namens:Nameüber Objekt
3.1 Gängige Ersetzungen
WÄHLEN Sie viele
-- mysql-async MySQL.Async.fetchAll( 'SELECT * FROM users WHERE identifier = @id', { ['@id'] = identifier }, function(rows) ... end ) -- oxmysql (Rückruf über Export) exports.oxmysql:query( 'SELECT * FROM users WHERE identifier = ?', { identifier }, function(rows) ... end ) -- oxmysql (await) local rows = MySQL.query.await( 'SELECT * FROM users WHERE identifier = ?', { identifier } )
SELECT einzelne Zeile
-- mysql-async (fetchAll + rows[1]) -- oxmysql lokale Zeile = MySQL.single.await( 'SELECT * FROM Benutzer WHERE Kennung = ?', { Kennung } )
SELECT-Skalar (z. B. Anzahl, ID)
-- mysql-async -- oxmysql lokale Anzahl = MySQL.scalar.await( 'SELECT COUNT(*) FROM owned_vehicles WHERE owner = ?', { owner } )
EINFÜGEN (InsertId abrufen)
-- mysql-async (ausführen) -- oxmysql local insertId = MySQL.insert.await( 'INSERT INTO notes (owner, text) VALUES (?, ?)', { cid, text } )
AKTUALISIEREN/LÖSCHEN (betroffene Zeilen)
-- mysql-async (ausführen) -- oxmysql local geändert = MySQL.update.await( 'UPDATE users SET job = ?, job_grade = ? WHERE identifier = ?', { job, grade, identifier } )
Transaktionen (Handbuch)
-- oxmysql manual transaction
MySQL.query.await('START TRANSACTION')
local ok = true
local r1 = MySQL.update.await('UPDATE users SET bank = bank - ? WHERE identifier = ? AND bank >= ?', { amount, fromId, amount })
local r2 = MySQL.update.await('UPDATE users SET bank = bank + ? WHERE identifier = ?', { amount, toId })
if r1 == 1 and r2 == 1 then
MySQL.query.await('COMMIT')
else
MySQL.query.await('ROLLBACK')
end
Einige Frameworks stellen Wrapper bereit (z. B.
ox_lib), die hinzufügenMySQL.ready,.Transaktion, usw. Die obigen Aufrufe sind ohne zusätzliche Wrapper sicher.
4) Spickzettel für vorbereitete Aussagen
Parameterstile
- mysql‑async (Legacy):
@NamePlatzhalter mit einer Tabelle:{ ['@name']=Wert } - oxmysql (positional):
?Platzhalter mit einem Array:{ Wert1, Wert2 } - oxmysql (benannt):
:NamePlatzhalter mit einem Objekt:{ Name = Wert }
Beispiele
-- Benannte Parameter (empfohlen für bessere Lesbarkeit) lokale Zeile = MySQL.single.await( 'SELECT * FROM users WHERE identifier = :id', { id = identifier } ) -- IN (...) Liste -- Platzhalter dynamisch erstellen und ein flaches Array übergeben lokale IDs = { 'cid1', 'cid2', 'cid3' } lokale qs = ('?,' ):rep(#ids):sub(1,-2) -- "?, ?, ?" lokale Zeilen = MySQL.query.await('SELECT * FROM players WHERE citizenid IN ('..qs..')', ids) – JSON-Felder (MySQL 5.7+/MariaDB 10.2+) lokaler Name = MySQL.scalar.await('SELECT JSON_UNQUOTE(JSON_EXTRACT(data, "$.name")) FROM players WHERE citizenid = ?', { cid })
Tun
- Verwenden vorbereitete Aussagen überall (niemals Zeichenfolgenverkettung von Benutzereingaben).
- Bevorzugen benannte Parameter für Klarheit in komplexen Aussagen.
- Hinzufügen GRENZE 1 beim Lesen einer einzelnen Entität.
Vermeiden
- Wildcard
WÄHLEN *in Hot Paths (vom Projekt benötigte Spalten). - N+1 Abfragen pro Zeile; Batch mit
IN (...).
5) Datenbank-„UP“-Migrationsskripte (Ready-to-Run)
Wählen Sie die Blöcke, die zu Ihrem Framework (ESX/QBCore) und Server (MySQL 8+ oder MariaDB 10.4+) passen. Führen Sie die Ausführung zuerst auf der Staging-Plattform aus.
5.1 Zeichensatz und Sortierung normalisieren (überall UTF‑8)
(A) MySQL 8+ - ersetzen Ihre Datenbank einmal
-- Datenbankstandard auf utf8mb4 erzwingen (Emoji-sicher) ALTER DATABASE `yourdb` CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -- Gemeinsame Tabellen konvertieren (Liste nach Bedarf erweitern) ALTER TABLE `users` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE `owned_vehicles` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE `players` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE `player_vehicles` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
(B) MariaDB 10.4+ — dieselben Aussagen gelten.
Fügen Sie weitere Hot Tables (Inventar, Abrechnung, Telefon, Gesellschaft, Jobs) hinzu, sofern diese auf Ihrem Server vorhanden sind.
5.2 ESX-Indizes (sichere Leistung gewinnt)
MySQL 8+
ALTER TABLE `users` ADD INDEX IF NOT EXISTS `idx_users_identifier` (`Kennung`), ADD INDEX IF NOT EXISTS `idx_users_job` (`Job`), ADD INDEX IF NOT EXISTS `idx_users_name` (`Name`); ALTER TABLE `owned_vehicles` ADD UNIQUE INDEX IF NOT EXISTS `ux_owned_vehicles_plate` (`Kennzeichen`), ADD INDEX IF NOT EXISTS `idx_owned_vehicles_owner` (`Eigentümer`);
MariaDB 10.4+
-- Zuerst löschen, um idempotent zu sein, wenn IF NOT EXISTS nicht verfügbar ist. DROP INDEX IF EXISTS `idx_users_identifier` ON `users`; DROP INDEX IF EXISTS `idx_users_job` ON `users`; DROP INDEX IF EXISTS `idx_users_name` ON `users`; CREATE INDEX `idx_users_identifier` ON `users` (`identifier`); CREATE INDEX `idx_users_job` ON `users` (`job`); CREATE INDEX `idx_users_name` ON `users` (`name`); DROP INDEX IF EXISTS `ux_owned_vehicles_plate` ON `owned_vehicles`; DROP INDEX IF EXISTS `idx_owned_vehicles_owner` ON `owned_vehicles`; ERSTELLEN SIE DEN EINDEUTIGEN INDEX `ux_owned_vehicles_plate` FÜR `owned_vehicles` (`plate`); ERSTELLEN SIE DEN INDEX `idx_owned_vehicles_owner` FÜR `owned_vehicles` (`owner`);
5.3 QBCore/QBOX-Indizes
MySQL 8+
ALTER TABLE `players` ADD UNIQUE INDEX IF NOT EXISTS `ux_players_citizenid` (`citizenid`), ADD INDEX IF NOT EXISTS `idx_players_license` (`license`), ADD INDEX IF NOT EXISTS `idx_players_steam` (`steam`), ADD INDEX IF NOT EXISTS `idx_players_last_name` (`lastname`); ALTER TABLE `player_vehicles` ADD UNIQUE INDEX IF NOT EXISTS `ux_player_vehicles_plate` (`plate`), ADD INDEX IF NOT EXISTS `idx_player_vehicles_citizenid` (`citizenid`);
MariaDB 10.4+
INDEX LÖSCHEN, WENN `ux_players_citizenid` AUF `players` VORHANDEN IST; INDEX LÖSCHEN, WENN `idx_players_license` AUF `players` VORHANDEN IST; INDEX LÖSCHEN, WENN `idx_players_steam` AUF `players` VORHANDEN IST; INDEX LÖSCHEN, WENN `idx_players_last_name` AUF `players` VORHANDEN IST; EINDEUTIGEN INDEX ERSTELLEN `ux_players_citizenid` AUF `players` (`citizenid`); INDEX ERSTELLEN `idx_players_license` AUF `players` (`license`); INDEX ERSTELLEN `idx_players_steam` AUF `players` (`steam`); INDEX ERSTELLEN `idx_players_last_name` AUF `players` (`lastname`); DROP INDEX IF EXISTS `ux_player_vehicles_plate` ON `player_vehicles`; DROP INDEX IF EXISTS `idx_player_vehicles_citizenid` ON `player_vehicles`; CREATE UNIQUE INDEX `ux_player_vehicles_plate` ON `player_vehicles` (`plate`); CREATE INDEX `idx_player_vehicles_citizenid` ON `player_vehicles` (`citizenid`);
5.4 Optional: ox_inventory (falls installiert)
ALTER TABLE `ox_inventory` ADD INDEX IF NOT EXISTS `idx_inv_owner` (`Besitzer`), ADD INDEX IF NOT EXISTS `idx_inv_type` (`Typ`); ALTER TABLE `ox_inventory_items` ADD INDEX IF NOT EXISTS `idx_items_inv_owner_name` (`Inventar`, `Besitzer`, `Name`);
Passen Sie die Tabellennamen an, wenn Ihr Schema abweicht (einige Setups verwenden
Vorräte/Artikel).
6) Rollback-Plan (Zero‑Panic)
6.1 Code-Rollback
- Machen Sie Ihre Ressourcenänderungen rückgängig (behalten Sie eine
Legacy-MySQL-AsyncZweig). - In
server.cfgtauschen:# stellt sicher, dass Oxmysql MySQL-asynchron ist - Starten Sie FXServer oder die betroffenen Ressourcen in Abhängigkeitsreihenfolge neu.
6.2 SQL-Rollback
- Wenn Sie nur hinzugefügte Indizes: lassen Sie sie fallen (siehe MariaDB Blöcke oben – verwenden
INDEX LÖSCHEN, WENN VORHANDEN). - Wenn du Zeichensatz/Sortierung geändert und muss die DB und Tabellen rückgängig machen und zurücksetzen:
ALTER DATABASE `yourdb` CHARACTER SET = utf8 COLLATE = utf8_general_ci; ALTER TABLE `users` KONVERTIEREN IN CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE `owned_vehicles` KONVERTIEREN IN CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE `players` KONVERTIEREN IN CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE `player_vehicles` KONVERTIEREN IN CHARACTER SET utf8 COLLATE utf8_general_ci;
Bevorzugen Sie die Wiederherstellung von backup.sql anstelle von Massenumkehrungen des Zeichensatzes, wenn möglich.
7) End-to-End-Migrationsverfahren (skriptfähig)
- Bereitstellungen einfrieren, DB sichern.
- Anwenden Abschnitt 5 „UP“ SQL bei Staging → Verifizieren → Prod.
- Commit-Code-Refactoring: Aufrufe (Abschnitt 3) + Parameterstile (Abschnitt 4) ersetzen.
- Einsetzen,
stellen Sie Oxmysql sicher, starten Sie den Server neu. - Laufen Rauchtests (Anmeldung, Gehaltsschecks, Inventar, Fahrzeug-Spawn/Despawn, Sperren, Gesellschaftsgeld, Umschalten der Job-Aufgaben).
- Beobachten Sie die Protokolle 15–30 Minuten lang (
mysql_slow_query_warninghilft); beheben Sie alle fehlenden Parameter oder Schema-Nichtübereinstimmungen.
8) Mikro-Benchmarks (Bringen Sie Ihre eigenen Zahlen mit)
Eine kleine Ressource, die Sie zum Vergleichen von Hot‑Path‑Abfragen verwenden können auf dein Hardware und Datensatz.
fxmanifest.lua
fx_version 'cerulean' Spiel 'gta5' Server_script 'bench.lua'
Bank.lua
local COUNT = 2000 -- adjust for your server
local function bench(name, fn)
local t0 = os.clock()
local ok, err = pcall(fn)
local dt = (os.clock() - t0) * 1000.0
print(('[bench] %s: %.2f ms %s'):format(name, dt, ok and '' or ('ERR: '..tostring(err))))
end
-- Hot path 1: ownership lookup
bench('SELECT single', function()
for i=1,COUNT do
local row = MySQL.single.await('SELECT owner FROM owned_vehicles WHERE plate = :p LIMIT 1', { p = ('TEST%04d'):format(i % 500) })
end
end)
-- Hot path 2: batched fetch
bench('SELECT batch IN', function()
local ids = {}
for i=1,100 do ids[#ids+1] = ('cid%04d'):format(i) end
local qs = ('?,' ):rep(#ids):sub(1,-2)
local rows = MySQL.query.await('SELECT citizenid, firstname, lastname FROM players WHERE citizenid IN ('..qs..')', ids)
end)
-- Hot path 3: update with guard
bench('UPDATE guarded', function()
for i=1,COUNT do
local changed = MySQL.update.await('UPDATE users SET bank = bank + :d WHERE identifier = :id AND bank >= 0', { d = 1, id = ('lic:%04d'):format(i % 500) })
end
end)
So läuft man
- Legen Sie die Ressource in einen Ordner (z. B.
Ochsenbank/), hinzufügenOchsenbank sicherstellenZuserver.cfg. - Tail-Serverkonsole; Ergebnisse werden als Zeilen wie diese gedruckt:
[bench] SELECT einzeln: 134,21 ms. - Für eine vorher/nachher Vergleich, einmal ausführen mit
mysql-async(ggf. die Rufe anpassen), dann mitoxmysql.
Worauf Sie achten sollten
- Geringere Gesamt-ms pro Abschnitt nach der Migration.
- Geringere P95/P99-Latenz bei Gameplay-Aktionen, die an Abfragen gebunden sind.
- Weniger Warnungen vor langsamen Abfragen bei einer Stunde Live-Spiel.
9) Fehlerbehebung
F: Ich erhalte die Meldung „Kein solcher Export: Abfrage/Einzel/…“.
A: oxmysql nicht früh genug begonnen wird. Stellen Sie sicher stellen Sie Oxmysql sicher steht über den Ressourcen, die es verwenden.
F: Parameterfehler oder leere Ergebnisse.
A: Sie haben wahrscheinlich @param Platzhalter. Ersetzen Sie durch ? oder :Name und übergeben Sie entsprechend ein Array/Objekt.
F: Deadlocks oder teilweise Schreibvorgänge.
A: Fassen Sie mehrstufige Salden/Überweisungen in eine Transaktion zusammen (siehe Abschnitt 3), fügen Sie Indizes aus Abschnitt 5 hinzu.
F: JSON-Pfad gibt NULL zurück.
A: Stellen Sie sicher, dass Ihre Engine JSON-Funktionen unterstützt (MySQL ≥5.7/MariaDB ≥10.2) und dass der Spaltentyp JSON (nicht LANGTEXT).
F: Langsam nach der Migration.
A: Fehlende Indizes prüfen, ERKLÄREN Ihre Abfrage, projizieren Sie nur die benötigten Spalten und überprüfen Sie das Playbook zur Serveroptimierung.
10) Checkliste zur Codeüberprüfung (Kopieren/Einfügen)
- Kein SQL mit verketteten Zeichenfolgen; alle Abfragen parametrisiert.
- Verwenden
.einzel/.scalarmitGRENZE 1wenn nur eine Zeile/ein Wert erforderlich ist. - Charge
IN (...)liest für Sammlungen. - Transaktionen rund um mehrstufige Geld-/Inventarvorgänge.
- Index für jedes Hot vorhanden
WO/VERBINDENSpalte. - Vermeiden
WÄHLEN *in heißen Pfaden. - Protokollieren Sie langsame Abfragen und verfolgen Sie die häufigsten Verstöße wöchentlich.
Interne Links
- FiveM-Serveroptimierung: Das definitive Playbook für 2025 — https://fivemx.com/fivem-server-optimization/
- Adaptermuster: ESX↔QBCore↔QBOX-Exporte, Ereignisse und Player-Modelle — https://fivemx.com/adapter-patterns/
Credits
Betreut von fivemx.com. Beiträge sind willkommen (senden Sie Diffs von zusätzlichen sicheren Indizes oder Wrapper-Helfern).






