mysql async oxmysql

Migracja z mysql-async na oxmysql: Bezpieczna migracja i optymalizacja zapytań…

Publiczność: FiveM właściciele serwerów, skrypterzy, administratorzy
Bramka: Zastępować mysql-async z oxmysql bezpiecznie, przyspieszaj zapytania i modernizuj swoje użycie SQL.

Przeczytaj także:


TL;DR

  • Używać oxmysql: przygotowane zapytania, API promise\/await, lepsza diagnostyka, wysoka wydajność.
  • Minimalne zmiany w kodzie: zamień @param? (pozycyjne) lub :name (nazwane) parametry; zastąp MySQL.Async.* wywołania z MySQL.*/exports.oxmysql:*.
  • Uruchom skrypty SQL “UP” poniżej (poprawki zestawów znaków\/indeksów) i zachowaj rollback pod ręką.
  • Zweryfikuj za pomocą narzędzia micro‑benchmark na końcu, aby potwierdzić korzyści na swoim sprzęcie.

1) Lista kontrolna bezpieczeństwa przed lotem

  1. Pełna kopia zapasowa: mysqldump --single-transaction yourdb > backup.sql.
  2. Środowisko stagingowe odwzorowanie schematu produkcyjnego + podzbiór danych.
  3. Artifact i zależności: Aktualna kompilacja FXServer, najnowsza oxmysql.
  4. Okno przestoju dla przełączenia na produkcję (zwykle < 5 minut).
  5. Sondy stanu gotowe: /players, przepływ logowania, operacje ekonomiczne, operacje garażu, operacje ekwipunku, sprawdzanie banów.

2) Instalacja i podłączenie oxmysql

2.1 server.cfg

# Stop using mysql-async
default_prio 500
# ensure mysql-async # ← comment out or remove

# Start oxmysql
default_prio 50
ensure oxmysql

# Connection string consumed by oxmysql
set mysql_connection_string "mysql://user:pass@127.0.0.1:3306/yourdb?charset=utf8mb4"

# Optional diagnostics
set mysql_slow_query_warning 200 # log queries slower than 200ms
set mysql_debug false # true for verbose logging during staging

Trzymać mysql-async wyłączony, ale dostępny w folderze zasobów podczas fazy przejściowej (do szybkiego wycofania).

3) Mapowanie API: mysql‑async → oxmysql

mysql-async (starszy):

  • Asynchroniczny: MySQL.Async.fetchAll, MySQL.Async.fetchScalar, MySQL.Async.execute
  • Synchronizacja: MySQL.Sync.fetchAll, MySQL.Sync.fetchScalar, MySQL.Sync.execute
  • Parametry: @param styl tabeli jak { ['@identifier']=identifier }

oxmysql (nowoczesny):

  • Styl callback przez eksport: exports.oxmysql:query|scalar|single|insert|update(sql, params, cb)
  • Promise/await przez globalny: MySQL.query|scalar|single|insert|update.await(sql, params) i wywołania zwrotne bez await .await
  • Parametry: pozycyjne ? przez tablicę, lub nazwany :name przez obiekt

3.1 Typowe zamienniki

SELECT wiele

-- 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 pojedynczy wiersz

-- mysql-async (fetchAll + rows[1])

-- oxmysql
local row = MySQL.single.await(
 'SELECT * FROM users WHERE identifier = ?',
 { identifier }
)

SELECT skalarny (np. count, id)

-- mysql-async

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

INSERT (pobierz insertId)

-- mysql-async (execute)

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

UPDATE/DELETE (affectedRows)

-- mysql-async (execute)

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

Transakcje (ręczny)

-- 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

Niektóre frameworki udostępniają otoczki (np., ox_lib), które dodają MySQL.ready, .transaction, itp. Powyższe wywołania są bezpieczne bez dodatkowych otoczek.

4) Ściągawka dotycząca przygotowanych zapytań

Style parametrów

  • mysql‑async (przestarzałe): @name symbole zastępcze z tabelą: { ['@name']=value }
  • oxmysql (pozycyjne): ? symbole zastępcze z tablicą: { value1, value2 }
  • oxmysql (nazwane): :name symbole zastępcze z obiekt: { name = value }

Przykłady

-- Named params (recommended for readability)
local row = MySQL.single.await(
 'SELECT * FROM users WHERE identifier = :id',
 { id = identifier }
)

-- IN (...) list
-- Build placeholders dynamically and pass a flat 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 fields (MySQL 5.7+/MariaDB 10.2+)
local name = MySQL.scalar.await('SELECT JSON_UNQUOTE(JSON_EXTRACT(data, "$.name")) FROM players WHERE citizenid = ?', { cid })

Nie

  • Używać przygotowane zapytania wszędzie (nigdy nie łącz ciągów znaków z danymi od użytkownika).
  • Woleć parametry nazwane dla przejrzystości w złożonych zapytaniach.
  • Dodaj LIMIT 1 podczas odczytu pojedynczej encji.

Unikaj

  • Symbol wieloznaczny SELECT * w gorących ścieżkach (wymagane kolumny projektu).
  • Zapytania N+1 na wiersz; grupuj w pakiety za pomocą IN (...).

5) Skrypty migracji bazy danych „UP” (gotowe do uruchomienia)

Wybierz bloki pasujące do Twojego frameworka (ESX/QBCore) i serwera (MySQL 8+ lub MariaDB 10.4+). Najpierw uruchom na staging. .

5.1 Normalizacja zestawu znaków i kodowania (wszędzie UTF‑8)

(A) MySQL 8+ — zastąp yourdb raz

-- Force database default to utf8mb4 (emoji‑safe)
ALTER DATABASE `yourdb` CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;

-- Convert common tables (extend list as needed)
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+ — te same instrukcje są poprawne.

Dodaj inne popularne tabele (inventory, billing, phone, society, jobs) obecne na Twoim serwerze.

5.2 Indeksy ESX (bezpieczne zyski wydajności)

MySQL 8+

ALTER TABLE `users`
 ADD INDEX IF NOT EXISTS `idx_users_identifier` (`identifier`),
 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` (`plate`),
 ADD INDEX IF NOT EXISTS `idx_owned_vehicles_owner` (`owner`);

MariaDB 10.4+

-- Drop first to be idempotent where IF NOT EXISTS is unavailable
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`;
CREATE UNIQUE INDEX `ux_owned_vehicles_plate` ON `owned_vehicles` (`plate`);
CREATE INDEX `idx_owned_vehicles_owner` ON `owned_vehicles` (`owner`);

5.3 QBCore/QBOX Indeksy

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+

DROP INDEX IF EXISTS `ux_players_citizenid` ON `players`;
DROP INDEX IF EXISTS `idx_players_license` ON `players`;
DROP INDEX IF EXISTS `idx_players_steam` ON `players`;
DROP INDEX IF EXISTS `idx_players_last_name` ON `players`;
CREATE UNIQUE INDEX `ux_players_citizenid` ON `players` (`citizenid`);
CREATE INDEX `idx_players_license` ON `players` (`license`);
CREATE INDEX `idx_players_steam` ON `players` (`steam`);
CREATE INDEX `idx_players_last_name` ON `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 Opcjonalnie: ox_inventory (jeśli zainstalowano)

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`);

Dostosuj nazwy tabel, jeśli twój schemat się różni (niektóre konfiguracje używają inventories / items).

6) Plan wycofania (Zero‑Panic)

6.1 Wycofanie kodu

  1. Cofnij zmiany w zasobach (zachowaj legacy-mysql-async branch).
  2. W server.cfg zamień: # ensure oxmysql ensure mysql-async
  3. Uruchom ponownie FXServer lub dotknięte zasoby w kolejności zależności.

6.2 SQL wycofanie

  • Jeśli tylko dodane indeksy: usuń je (patrz MariaDB blocks above — use DROP INDEX IF EXISTS).
  • Jeśli zmieniono zestaw znaków/kolekcję i muszą cofnąć, przywrócić bazę danych i tabele:
ALTER DATABASE `yourdb` CHARACTER SET = utf8 COLLATE = utf8_general_ci;
ALTER TABLE `users` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
ALTER TABLE `owned_vehicles` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
ALTER TABLE `players` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
ALTER TABLE `player_vehicles` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;

Preferuj przywracanie z backup.sql instead of mass charset reversals when possible.

7) Procedura migracji od początku do końca (skryptowalna)

  1. Freeze wdraża, wykonaj kopię zapasową bazy danych.
  2. Zastosuj Sekcja 5 „UP” SQL na staging → weryfikuj → prod.
  3. Refaktoryzacja kodu commit: zamień wywołania (Sekcja 3) + style parametrów (Sekcja 4).
  4. Deploy, ensure oxmysql, uruchom ponownie serwer.
  5. Uruchom testy dymne (logowanie, odcinki wypłat, ekwipunek, pojawianie się/znikanie pojazdów, bany, pieniądze społeczne, przełączniki obowiązków w pracy).
  6. Obserwuj logi przez 15-30 minut (mysql_slow_query_warning pomaga); adresuj wszelkie pominięte parametry lub niezgodności schematu.

8) Mikro-benchmarki (Przynieś własne liczby)

A tiny resource you can drop in to compare hot‑path queries on Twój sprzęt i zestaw danych.

fxmanifest.lua

fx_version 'cerulean'
game 'gta5'
server_script 'bench.lua'

bench.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)

Jak uruchomić

  1. Umieść zasób w folderze (np., ox-bench/), add ensure ox-bench do server.cfg.
  2. Śledź konsolę serwera; wyniki są drukowane jako linie typu: [bench] SELECT single: 134.21 ms.
  3. Dla przed/po porównanie, uruchom raz z mysql-async (w razie potrzeby dostosuj wywołania), a następnie z oxmysql.

What to look for

  • Niższy całkowity czas ms na sekcję po migracji.
  • Obniż opóźnienia P95/P99 w akcjach rozgrywki powiązanych z zapytaniami.
  • Mniej ostrzeżeń o wolnych zapytaniach przez godzinę gry na żywo.

9) Rozwiązywanie problemów

P: Otrzymuję „no such export: query/single/…”.
A: oxmysql nie został uruchomiony wystarczająco wcześnie. Upewnij się ensure oxmysql jest powyżej zasobów, które go używają.

P: Błędy parametrów lub puste wyniki.
O: Prawdopodobnie zostawiłeś @param miejsca zastępcze. Zastąp przez ? Lub :name i odpowiednio przekaż tablicę/obiekt.

P: Blokady lub częściowe zapisy.
O: Owiń wieloetapowe salda/transfery w transakcję (patrz Sekcja 3), dodaj indeksy z Sekcji 5.

P: JSON ścieżka zwraca NULL.
O: Potwierdź, że Twój silnik obsługuje funkcje JSON (MySQL ≥5.7/MariaDB ≥10.2) i że typ kolumny to JSON (nie LONGTEXT).

P: Wolne działanie po migracji.
O: Sprawdź brakujące indeksy, EXPLAIN zapytanie, wybieraj tylko potrzebne kolumny i przejrzyj Podręcznik optymalizacji serwera.

10) Lista kontrolna przeglądu kodu (kopiuj/wklej)

  • Brak konkatenacji ciągów znaków SQL; wszystkie zapytania sparametryzowane.
  • Używać .single/.scalar z LIMIT 1 gdy wymagany jest tylko jeden wiersz/wartość.
  • Partia IN (...) odczyty dla kolekcji.
  • Transakcje wokół wieloetapowych operacji pieniężnych/inwentaryzacyjnych.
  • Indeks obecny dla każdego gorącego WHERE/JOIN column.
  • Unikaj SELECT * na ścieżkach krytycznych.
  • Loguj wolne zapytania; śledź głównych winowajców co tydzień.

Linki wewnętrzne


Podziękowania
Utrzymywane przez fivemx.com. Mile widziane wkłady (prześlij różnice dodatkowych bezpiecznych indeksów lub pomocników opakowujących).