
Da mysql-async a oxmysql: Migrazione Sicura e Ottimizzazione delle Query…
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:
- Ottimizzazione del server FiveM: il manuale definitivo per il 2025 — https://fivemx.com/fivem-server-optimization/
- Modelli di adattatore: esportazioni ESX↔QBCore↔QBOX, eventi e modelli di player — https://fivemx.com/adapter-patterns/
In breve
- Utilizzo
oxmysql: istruzioni preparate, API promise/await, diagnostica migliore, prestazioni elevate. - Modifiche minime al codice: scambio
@param→?(posizionale) o:nome(nominati) parametri; sostituisciMySQL.Async.*chiamate conMySQL.*/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
- Backup completo:
mysqldump --single-transaction yourdb > backup.sql. - Ambiente di messa in scena schema di produzione speculare + sottoinsieme di dati.
- Artefatto e dipendenze: Build FXServer attuale, ultima
oxmysql. - Finestra di inattività per il cambio di produzione (solitamente < 5 minuti).
- 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-asincronodisabilitato 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:
@paramtabelle 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:nometramite 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)
-- transazione manuale oxmysql
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 aggiungonoMySQL.ready,.transazione, ecc. Le chiamate sopra sono sicure senza wrapper aggiuntivi.
4) Foglio riassuntivo delle dichiarazioni preparate
Stili dei parametri
- mysql‑async (legacy):
@nomesegnaposto con una tabella:{ ['@name']=valore } - oxmysql (posizionale):
?segnaposto con un vettore:{ valore1, valore2 } - oxmysql (denominato):
:nomesegnaposto 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
- Annulla le modifiche alle risorse (mantieni un
legacy-mysql-asyncramo). - In
server.cfgscambio:# assicura che oxmysql garantisca mysql-async - 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)
- Distribuzioni di congelamento, esegui il backup del DB.
- Fare domanda a Sezione 5 “UP” SQL in fase di staging → verifica → prod.
- Refactoring del codice commit: sostituzione delle chiamate (Sezione 3) + stili dei parametri (Sezione 4).
- Distribuire,
garantire oxmysql, riavviare il server. - Correre test del fumo (accesso, stipendi, inventario, comparsa/scomparsa dei veicoli, divieti, denaro della società, cambi di mansioni).
- Osservare i registri per 15–30 minuti (
mysql_slow_query_warningaiuta); 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
- Metti la risorsa in una cartella (ad esempio,
banco di bue/), aggiungereassicurare il banco dei buoiAserver.cfg. - Console del server di coda; i risultati vengono stampati come righe come:
[panchina] SELEZIONA singolo: 134,21 ms. - Per un prima/dopo confronto, esegui una volta con
mysql-asincrono(regolare le chiamate se necessario), quindi conoxmysql.
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/.scalareconLIMITE 1quando è 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/GIUNTURAcolonna. - Evitare
SELEZIONA *nei percorsi caldi. - Registra le query lente; monitora settimanalmente i principali trasgressori.
Link interni
- Ottimizzazione del server FiveM: il manuale definitivo per il 2025 — https://fivemx.com/fivem-server-optimization/
- Modelli di adattatore: esportazioni ESX↔QBCore↔QBOX, eventi e modelli di player — https://fivemx.com/adapter-patterns/
Crediti
Gestito da fivemx.com. I contributi sono benvenuti (inviare differenze di indici sicuri aggiuntivi o wrapper helper).






