Spare 20 % mit WELCOMEAngebote ansehen
MySQL asynchron oxmysql

Von mysql-async zu oxmysql: Sichere Migration & Abfrage-P…

Publikum: FiveM-Serverbesitzer, -Skripter und -Betreuer
Ziel: Ersetzen mysql-async mit oxmysql sicher, beschleunige Abfragen und modernisiere deine SQL-Nutzung.

Lies auch:


Kurz zusammengefasst

  • Verwenden oxmysql: vorbereitete Anweisungen, Promise/Await-API, bessere Diagnose, starke Leistung.
  • Minimale Codeänderungen: tauschen @param? (positional) oder :Name (benannte) Parameter; ersetzen MySQL.Async.* Anrufe mit MySQL.*/exports.oxmysql:*.
  • Führe die SQL-“UP”-Skripte aus unten (charset/index-Fixes) und behalte die Rollback praktisch.
  • Überprüfe mit dem Mikro-Benchmark-Tool am Ende, um Gewinne auf deiner Hardware zu bestätigen.

1) Sicherheitscheckliste vor dem Flug

  1. Vollständige Sicherung: mysqldump --single-transaction yourdb > backup.sql.
  2. Staging-Umgebung Spiegelung des Produktionsschemas + Datenteilmenge.
  3. Artefakt & Abhängigkeiten: Aktueller FXServer-Build, neueste oxmysql.
  4. Ausfallzeitfenster für Produktwechsel (normalerweise < 5 Minuten).
  5. Integritätstests bereit: /Spieler, Anmeldefluss, Wirtschaftsoperationen, Garagenoperationen, Inventaroperationen, Sperrprüfungen.

2) Installieren und Verkabeln oxmysql

2.1 server.cfg

# Höre auf, mysql-async zu verwenden\ndefault_prio 500\n# sicherstellen, dass mysql-async # ← auskommentieren oder entfernen\n\n# Starte oxmysql\ndefault_prio 50\nensure oxmysql\n\n# Verbindungszeichenfolge, die von oxmysql verwendet wird\nset mysql_connection_string "mysql://user:pass@127.0.0.1:3306/yourdb?charset=utf8mb4"\n\n# Optionale Diagnostik\nset mysql_slow_query_warning 200 # Protokolliere Abfragen, die langsamer als 200 ms sind\nset mysql_debug false # true für ausführliche Protokollierung während der Staging

Halten mysql-async deaktiviert, aber in deinem Ressourcenordner während der Staging-Phase 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: @param Stiltabellen 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ähle viele

-- mysql-async
MySQL.Async.fetchAll(
 'SELECT * FROM users WHERE identifier = @id',
 { ['@id'] = identifier },
 function(rows) ... end
)

-- oxmysql (callback via 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
local row = MySQL.single.await(
 'SELECT * FROM users WHERE identifier = ?',
 { identifier }
)

SELECT-Skalar (z. B. Anzahl, ID)

-- mysql-async

-- oxmysql
local count = MySQL.scalar.await(
 'SELECT COUNT(*) FROM owned_vehicles WHERE owner = ?',
 { owner }
)

EINFÜGEN (InsertId abrufen)

-- mysql-async (execute)

-- oxmysql
local insertId = MySQL.insert.await(
 'INSERT INTO notes (owner, text) VALUES (?, ?)',
 { cid, text }
)

AKTUALISIEREN/LÖSCHEN (betroffene Zeilen)

-- mysql-async (execute)

-- oxmysql
local changed = MySQL.update.await(
 'UPDATE users SET job = ?, job_grade = ? WHERE identifier = ?',
 { job, grade, identifier }
)

Transaktionen (Handbuch)

-- oxmysql manuelle Transaktion
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ügen MySQL.ready, .Transaktion, usw. Die obigen Aufrufe sind ohne zusätzliche Wrapper sicher.

4) Spickzettel für vorbereitete Aussagen

Parameterstile

  • mysql‑async (Legacy): @Name Platzhalter mit einer Tabelle: { ['@name']=Wert }
  • oxmysql (positional): ? Platzhalter mit einem Array: { Wert1, Wert2 }
  • oxmysql (benannt): :Name Platzhalter mit einem Objekt: { Name = Wert }

Beispiele

-- Benannte Parameter (empfohlen für Lesbarkeit)
local row = MySQL.single.await(
 'SELECT * FROM users WHERE identifier = :id',
 { id = identifier }
)

-- IN (...) Liste
-- Baue Platzhalter dynamisch auf und übergebe ein flaches Array
local ids = { 'cid1','cid2','cid3' }
local qs = ('?,' ):rep(#ids):sub(1,-2) -- "?, ?, ?"
local rows = MySQL.query.await('SELECT * FROM players WHERE citizenid IN ('..qs..')', ids)

-- JSON-Felder (MySQL 5.7+/MariaDB 10.2+)
local 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ähle die Blöcke, die zu deinem Framework (ESX/QBCore) und Server (MySQL 8+ oder MariaDB 10.4+) passen. Führe sie zuerst auf der Staging-Umgebung aus.

5.1 Zeichensatz und Sortierung normalisieren (überall UTF‑8)

(A) MySQL 8+ - ersetzen deinedb 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üge andere heiße Tabellen (Inventar, Abrechnung, Telefon, Gesellschaft, Jobs) hinzu, so wie sie auf deinem Server vorhanden sind.

5.2 ESX-Indizes (sichere Leistung gewinnt)

MySQL 8+

ALTER TABLE `users`\n ADD INDEX IF NOT EXISTS `idx_users_identifier` (`identifier`),\n ADD INDEX IF NOT EXISTS `idx_users_job` (`job`),\n ADD INDEX IF NOT EXISTS `idx_users_name` (`name`);\n\nALTER TABLE `owned_vehicles`\n ADD UNIQUE INDEX IF NOT EXISTS `ux_owned_vehicles_plate` (`plate`),\n ADD INDEX IF NOT EXISTS `idx_owned_vehicles_owner` (`owner`);

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` (`owner`),
 ADD INDEX IF NOT EXISTS `idx_inv_type` (`type`);

ALTER TABLE `ox_inventory_items`
 ADD INDEX IF NOT EXISTS `idx_items_inv_owner_name` (`inventory`, `owner`, `name`);

Passe die Tabellennamen an, wenn dein Schema abweicht (einige Setups verwenden Vorräte / Artikel).

6) Rollback-Plan (Zero‑Panic)

6.1 Code-Rollback

  1. Storniere deine Ressourcenänderungen (behalte eine Legacy-MySQL-Async Zweig).
  2. In server.cfg tauschen: # stellt sicher, dass Oxmysql MySQL-asynchron ist
  3. Starte FXServer oder die betroffenen Ressourcen in Abhängigkeitsreihenfolge neu.

6.2 SQL-Rollback

  • Wenn du nur hinzugefügte Indizes: entferne sie (siehe die 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;

Bevorzuge die Wiederherstellung von backup.sql anstelle von Massenumkehrungen des Zeichensatzes, wenn möglich.

7) End-to-End-Migrationsverfahren (skriptfähig)

  1. Bereitstellungen einfrieren, DB sichern.
  2. Anwenden Abschnitt 5 „UP“ SQL bei Staging → Verifizieren → Prod.
  3. Commit-Code-Refactoring: Aufrufe (Abschnitt 3) + Parameterstile (Abschnitt 4) ersetzen.
  4. Einsetzen, stelle sicher, dass oxmysql, starte den Server neu.
  5. Laufen Rauchtests (Anmeldung, Gehaltsschecks, Inventar, Fahrzeug-Spawn/Despawn, Sperren, Gesellschaftsgeld, Umschalten der Job-Aufgaben).
  6. Beobachte die Logs für 15–30 Minuten (mysql_slow_query_warning hilft); behandle fehlende Parameter oder Schema-Unstimmigkeiten.

8) Mikro-Benchmarks (Bring Your Own Numbers)

Eine winzige Ressource, die du einbauen kannst, um Hot-Path-Abfragen zu vergleichen dein Hardware und Datensatz.

fxmanifest.lua

fx_version 'cerulean' Spiel 'gta5' Server_script 'bench.lua'

Bank.lua

local COUNT = 2000 -- passe es für deinen Server an

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: Eigentumsprüfung
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 = ('TESTd'):format(i % 500) })
 end
end)

-- Hot path 2: gebatchte Abfrage
bench('SELECT batch IN', function()
 local ids = {}
 for i=1,100 do ids[#ids+1] = ('cidd'):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 mit Schutz
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:d'):format(i % 500) })
 end
end)

So läuft man

  1. Lege die Ressource in einen Ordner (z. B., Ochsenbank/), hinzufügen Ochsenbank sicherstellen Zu server.cfg.
  2. Tail-Serverkonsole; Ergebnisse werden als Zeilen wie diese gedruckt: [bench] SELECT einzeln: 134,21 ms.
  3. Für eine vorher/nachher Vergleich, einmal ausführen mit mysql-async (ggf. die Rufe anpassen), dann mit oxmysql.

Worauf du achten solltest

  • 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 wird nicht früh genug gestartet. Stelle sicher, stelle sicher, dass oxmysql steht über den Ressourcen, die es verwenden.

F: Parameterfehler oder leere Ergebnisse.
A: Du hast wahrscheinlich @param Platzhalter. Ersetze sie durch ? oder :Name und gib ein Array/Objekt entsprechend weiter.

F: Deadlocks oder teilweise Schreibvorgänge.
A: Fasse mehrstufige Guthaben/Übertragungen in eine Transaktion zusammen (siehe Abschnitt 3), füge Indexe aus Abschnitt 5 hinzu.

F: JSON-Pfad gibt NULL zurück.
A: Bestätige, dass dein 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 deine Abfrage, projiziere nur die benötigten Spalten und überprüfe das Server-Optimierungs-Handbuch.

10) Checkliste zur Codeüberprüfung (Kopieren/Einfügen)

  • Kein SQL mit verketteten Zeichenfolgen; alle Abfragen parametrisiert.
  • Verwenden .einzel/.scalar mit GRENZE 1 wenn 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/VERBINDEN Spalte.
  • Vermeiden WÄHLEN * in heißen Pfaden.
  • Protokolliere langsame Abfragen; verfolge die größten Übeltäter wöchentlich.

Interne Links


Credits
Wird gepflegt von fivemx.com. Beiträge sind willkommen (sende Diffs zusätzlicher sicherer Indizes oder Wrapper-Hilfen).