Risparmia oggi con 20% Usa il codice WELCOME al pagamento. WELCOME

From mysql-async to oxmysql: Safe Migration & Query P…

Pubblico: Proprietari, programmatori e manutentori del server FiveM
Obiettivo: Sostituire mysql-asincrono con oxmysql in modo sicuro, velocizzare le query e modernizzare l'utilizzo di SQL.

Leggi anche:


In breve

  • Utilizzo oxmysql: istruzioni preparate, API promise/await, diagnostica migliore, prestazioni elevate.
  • Modifiche minime al codice: scambio @param? (posizionale) o :nome (nominati) parametri; sostituisci MySQL.Async.* chiamate con MySQL.*/esportazioni.oxmysql:*.
  • Eseguire gli script SQL "UP" di seguito (correzioni di charset/indice) e mantenere il rollback utile.
  • Verificare con il cablaggio micro-benchmark alla fine per confermare le vittorie sul tuo hardware.

1) Lista di controllo di sicurezza pre-volo

  1. Backup completo: mysqldump --single-transaction yourdb > backup.sql.
  2. Ambiente di messa in scena schema di produzione speculare + sottoinsieme di dati.
  3. Artefatto e dipendenze: Build FXServer attuale, ultima oxmysql.
  4. Finestra di inattività per il cambio di produzione (solitamente < 5 minuti).
  5. Sonde sanitarie pronto: /giocatori, flusso di accesso, operazioni economiche, operazioni di garage, operazioni di inventario, controlli di ban.

2) Installazione e cablaggio oxmysql

2.1 server.cfg

# Interrompere l'utilizzo di mysql-async default_prio 500 # garantire mysql-async # ← commentare o rimuovere # Avviare oxmysql default_prio 50 garantire oxmysql # Stringa di connessione utilizzata da oxmysql impostare mysql_connection_string "mysql://user:pass@127.0.0.1:3306/yourdb?charset=utf8mb4" # Diagnostica facoltativa impostare mysql_slow_query_warning 200 # Registrare le query più lente di 200 ms impostare mysql_debug false # true per la registrazione dettagliata durante la fase di staging

Mantenere mysql-asincrono disabilitato ma disponibile nella cartella delle risorse durante la fase di staging (per un rapido rollback).

3) Mappatura API: mysql‑async → oxmysql

mysql-asincrono (eredità):

  • Asincrono: MySQL.Async.fetchAll, MySQL.Async.fetchScalar, MySQL.Async.execute
  • Sincronizzazione: MySQL.Sync.fetchAll, MySQL.Sync.fetchScalar, MySQL.Sync.execute
  • Parametri: @param tabelle di stile come { ['@identificatore']=identificatore }

oxmysql (moderno):

  • Stile di richiamata tramite esportare: exports.oxmysql:query|scalar|single|insert|update(sql, params, cb)
  • Prometti/attendi tramite globale: MySQL.query|scalare|singolo|inserimento|aggiornamento.attesa(sql, parametri) e callback non in attesa senza .attendere
  • Parametri: posizionale ? tramite array, o nominato :nome tramite oggetto

3.1 Sostituzioni comuni

SELEZIONA molti

-- mysql-async MySQL.Async.fetchAll( 'SELECT * FROM users WHERE identifier = @id', { ['@id'] = identifier }, function(rows) ... end ) -- oxmysql (callback tramite 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 } )

SELEZIONA riga singola

-- mysql-async (fetchAll + rows[1]) -- oxmysql riga locale = MySQL.single.await( 'SELECT * FROM users WHERE identifier = ?', { identifier } )

SELEZIONA scalare (ad esempio, conteggio, id)

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

INSERIRE (ottieni insertId)

-- mysql-async (esegui) -- oxmysql local insertId = MySQL.insert.await( 'INSERISCI IN NOTES (owner, text) VALUES (?, ?)', { cid, text } )

AGGIORNA/ELIMINA (righe interessate)

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

Transazioni (manuale)

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

Alcuni framework espongono wrapper (ad esempio, ox_lib) che aggiungono MySQL.ready, .transazione, ecc. Le chiamate sopra sono sicure senza wrapper aggiuntivi.

4) Foglio riassuntivo delle dichiarazioni preparate

Stili dei parametri

  • mysql‑async (legacy): @nome segnaposto con una tabella: { ['@name']=valore }
  • oxmysql (posizionale): ? segnaposto con un vettore: { valore1, valore2 }
  • oxmysql (denominato): :nome segnaposto con un oggetto: { nome = valore }

Esempi

-- Parametri denominati (consigliati per una migliore leggibilità) riga locale = MySQL.single.await( 'SELECT * FROM users WHERE identifier = :id', { id = identifier } ) -- IN (...) elenco -- Crea segnaposto in modo dinamico e passa un array semplice ids locali = { 'cid1','cid2','cid3' } qs locali = ('?,' ):rep(#ids):sub(1,-2) -- "?, ?, ?" righe locali = MySQL.query.await('SELECT * FROM players WHERE citizenid IN ('..qs..')', ids) -- campi JSON (MySQL 5.7+/MariaDB 10.2+) nome locale = MySQL.scalar.await('SELECT JSON_UNQUOTE(JSON_EXTRACT(data, "$.name")) FROM players WHERE citizenid = ?', { cid })

Fare

  • Utilizzo dichiarazioni preparate ovunque (non concatenare mai l'input dell'utente).
  • Prefer parametri denominati per chiarezza in affermazioni complesse.
  • Aggiungere LIMITE 1 quando si legge una singola entità.

Evitare

  • Carattere jolly SELEZIONA * nei percorsi attivi (colonne necessarie al progetto).
  • Query N+1 per riga; batch con IN (...).

5) Script di migrazione del database "UP" (pronti per l'esecuzione)

Scegli i blocchi che corrispondono al tuo framework (ESX/QBCore) e al tuo server (MySQL 8+ o MariaDB 10.4+). Esegui prima in staging.

5.1 Normalizza set di caratteri e collazione (UTF‑8 ovunque)

(A) MySQL 8+ - sostituire il tuo database una volta

-- Forza il database predefinito su utf8mb4 (sicuro per le emoji) ALTER DATABASE `yourdb` CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -- Converti tabelle comuni (estendere l'elenco secondo necessità) ALTER TABLE `users` CONVERTI IN CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE `owned_vehicles` CONVERTI IN CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE `players` CONVERTI IN CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE `player_vehicles` CONVERTI IN CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

(B) MariaDB 10.4+ — le stesse affermazioni sono valide.

Aggiungi altre tabelle hot (inventario, fatturazione, telefono, società, lavori) se presenti sul tuo server.

5.2 Indici ESX (vincitori di prestazioni sicure)

MySQL 8+

ALTER TABLE `users` AGGIUNGI INDICE SE NON ESISTE `idx_users_identifier` (`identificatore`), AGGIUNGI INDICE SE NON ESISTE `idx_users_job` (`lavoro`), AGGIUNGI INDICE SE NON ESISTE `idx_users_name` (`nome`); ALTER TABLE `owned_vehicles` AGGIUNGI INDICE UNICO SE NON ESISTE `ux_owned_vehicles_plate` (`targa`), AGGIUNGI INDICE SE NON ESISTE `idx_owned_vehicles_owner` (`proprietario`);

MariaDB 10.4+

-- Elimina prima per essere idempotente dove IF NOT EXISTS non è disponibile 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`; CREA INDEX `idx_users_identifier` ON `users` (`identifier`); CREA INDEX `idx_users_job` ON `users` (`job`); CREA 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`; CREA INDICE UNICO `ux_owned_vehicles_plate` SU `owned_vehicles` (`plate`); CREA INDICE `idx_owned_vehicles_owner` SU `owned_vehicles` (`owner`);

5.3 Indici QBCore/QBOX

MySQL 8+

ALTER TABLE `players` AGGIUNGI INDICE UNIVOCO SE NON ESISTE `ux_players_citizenid` (`citizenid`), AGGIUNGI INDICE SE NON ESISTE `idx_players_license` (`license`), AGGIUNGI INDICE SE NON ESISTE `idx_players_steam` (`steam`), AGGIUNGI INDICE SE NON ESISTE `idx_players_last_name` (`lastname`); ALTER TABLE `player_vehicles` AGGIUNGI INDICE UNIVOCO SE NON ESISTE `ux_player_vehicles_plate` (`plate`), AGGIUNGI INDICE SE NON ESISTE `idx_player_vehicles_citizenid` (`citizenid`);

MariaDB 10.4+

ELIMINA INDICE SE ESISTE `ux_players_citizenid` SU `players`; ELIMINA INDICE SE ESISTE `idx_players_license` SU `players`; ELIMINA INDICE SE ESISTE `idx_players_steam` SU `players`; ELIMINA INDICE SE ESISTE `idx_players_last_name` SU `players`; CREA INDICE UNICO `ux_players_citizenid` SU `players` (`citizenid`); CREA INDICE `idx_players_license` SU `players` (`license`); CREA INDICE `idx_players_steam` SU `players` (`steam`); CREA INDICE `idx_players_last_name` SU `players` (`lastname`); ELIMINA INDICE SE ESISTE `ux_player_vehicles_plate` SU `player_vehicles`; ELIMINA INDICE SE ESISTE `idx_player_vehicles_citizenid` SU `player_vehicles`; CREA INDICE UNICO `ux_player_vehicles_plate` SU `player_vehicles` (`plate`); CREA INDICE `idx_player_vehicles_citizenid` SU `player_vehicles` (`citizenid`);

5.4 Facoltativo: ox_inventory (se installato)

ALTER TABLE `ox_inventory` AGGIUNGI INDICE SE NON ESISTE `idx_inv_owner` (`owner`), AGGIUNGI INDICE SE NON ESISTE `idx_inv_type` (`type`); ALTER TABLE `ox_inventory_items` AGGIUNGI INDICE SE NON ESISTE `idx_items_inv_owner_name` (`inventory`, `owner`, `name`);

Modifica i nomi delle tabelle se lo schema è diverso (alcune configurazioni utilizzano inventari / elementi).

6) Piano di rollback (Zero-Panic)

6.1 Rollback del codice

  1. Annulla le modifiche alle risorse (mantieni un legacy-mysql-async ramo).
  2. In server.cfg scambio: # assicura che oxmysql garantisca mysql-async
  3. Riavviare FXServer o le risorse interessate in ordine di dipendenza.

6.2 Rollback SQL

  • Se solo tu indici aggiunti: lasciarli cadere (vedi il MariaDB blocchi sopra — usa ELIMINA INDICE SE ESISTE).
  • Se tu set di caratteri/collazione modificati e bisogna annullare, ripristinare il DB e le tabelle:
ALTER DATABASE `yourdb` SET DI CARATTERI = utf8 COLLATE = utf8_general_ci; ALTER TABLE `users` CONVERTI IN SET DI CARATTERI utf8 COLLATE utf8_general_ci; ALTER TABLE `owned_vehicles` CONVERTI IN SET DI CARATTERI utf8 COLLATE utf8_general_ci; ALTER TABLE `players` CONVERTI IN SET DI CARATTERI utf8 COLLATE utf8_general_ci; ALTER TABLE `player_vehicles` CONVERTI IN SET DI CARATTERI utf8 COLLATE utf8_general_ci;

Preferisco ripristinare da backup.sql invece di inversioni di massa dei set di caratteri, quando possibile.

7) Procedura di migrazione end-to-end (programmabile)

  1. Distribuzioni di congelamento, esegui il backup del DB.
  2. Fare domanda a Sezione 5 “UP” SQL in fase di staging → verifica → prod.
  3. Refactoring del codice commit: sostituzione delle chiamate (Sezione 3) + stili dei parametri (Sezione 4).
  4. Distribuire, garantire oxmysql, riavviare il server.
  5. Correre test del fumo (accesso, stipendi, inventario, comparsa/scomparsa dei veicoli, divieti, denaro della società, cambi di mansioni).
  6. Osservare i registri per 15–30 minuti (mysql_slow_query_warning aiuta); risolvere eventuali parametri mancanti o incongruenze nello schema.

8) Micro-Benchmark (porta i tuoi numeri)

Una piccola risorsa a cui puoi accedere per confrontare le query hot-path su tuo hardware e set di dati.

fxmanifest.lua

fx_version 'cerulean' gioco '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)

Come correre

  1. Metti la risorsa in una cartella (ad esempio, banco di bue/), aggiungere assicurare il banco dei buoi A server.cfg.
  2. Console del server di coda; i risultati vengono stampati come righe come: [panchina] SELEZIONA singolo: 134,21 ms.
  3. Per un prima/dopo confronto, esegui una volta con mysql-asincrono (regolare le chiamate se necessario), quindi con oxmysql.

Cosa cercare

  • Riduzione del numero totale di ms per sezione dopo la migrazione.
  • Minore latenza P95/P99 nelle azioni di gioco legate alle query.
  • Meno avvisi di query lente dopo un'ora di riproduzione in diretta.

9) Risoluzione dei problemi

D: Ricevo il messaggio "nessuna esportazione: query/singola/...".
UN: oxmysql non è iniziato abbastanza presto. Assicurarsi garantire oxmysql è al di sopra delle risorse che lo utilizzano.

D: Errori nei parametri o risultati vuoti.
A: Probabilmente hai continuato @param segnaposto. Sostituisci con ? O :nome e passare un array/oggetto di conseguenza.

D: Deadlock o scritture parziali.
A: Inserire saldi/trasferimenti multi-step in una transazione (vedere Sezione 3), aggiungere indici dalla Sezione 5.

D: Il percorso JSON restituisce NULL.
A: Verifica che il tuo motore supporti le funzioni JSON (MySQL ≥5.7/MariaDB ≥10.2) e che il tipo di colonna sia JSON (non TESTO LUNGO).

D: Lento dopo la migrazione.
A: Controlla gli indici mancanti, SPIEGARE la tua query, le colonne necessarie al progetto e rivedi il Server Optimization Playbook.

10) Lista di controllo per la revisione del codice (copia/incolla)

  • Nessun SQL concatenato tramite stringhe; tutte le query sono parametrizzate.
  • Utilizzo .separare/.scalare con LIMITE 1 quando è richiesta solo una riga/valore.
  • Lotto IN (...) letture per collezioni.
  • Transazioni relative a operazioni di inventario/denaro in più fasi.
  • Indice presente per ogni caldo DOVE/GIUNTURA colonna.
  • Evitare SELEZIONA * nei percorsi caldi.
  • Registra le query lente; monitora settimanalmente i principali trasgressori.

Link interni


Crediti
Gestito da fivemx.com. I contributi sono benvenuti (inviare differenze di indici sicuri aggiuntivi o wrapper helper).

Luca
Luca

Mi chiamo Luke, sono un giocatore e amo scrivere di FiveM, GTA e giochi di ruolo. Gestisco una community di gioco di ruolo e ho circa 10 anni di esperienza nell'amministrazione di server.

Articoli: 436