From mysql-async to oxmysql: Safe Migration & Query P…
Public: Propriétaires, scripteurs et mainteneurs du serveur FiveM
But: Remplacer mysql-async avec oxmysql en toute sécurité, accélérez les requêtes et modernisez votre utilisation de SQL.
A lire aussi :
- Optimisation du serveur FiveM : le guide ultime pour 2025 — https://fivemx.com/fivem-server-optimization/
- Modèles d'adaptateur : Exportations, événements et modèles de lecteur ESX↔QBCore↔QBOX — https://fivemx.com/adapter-patterns/
TL;DR
- Utiliser
oxmysql: instructions préparées, API promise/wait, meilleurs diagnostics, performances élevées. - Modifications minimales du code: échanger
@param→?(positionnel) ou:nomparamètres (nommés) ; remplacerMySQL.Async.*appels avecMySQL.*/exportations.oxmysql:*. - Exécutez les scripts SQL « UP » ci-dessous (corrections de jeu de caractères/index) et conserver le retour en arrière pratique.
- Vérifier avec le harnais micro-benchmark à la fin pour confirmer les gains sur votre matériel.
1) Liste de contrôle de sécurité avant le vol
- Sauvegarde complète:
mysqldump --single-transaction yourdb > backup.sql. - Environnement de mise en scène mise en miroir du schéma de production + sous-ensemble de données.
- Artefact et dépendances: Version actuelle de FXServer, la plus récente
oxmysql. - Fenêtre d'arrêt pour le changement de produit (généralement < 5 minutes).
- Sondes de santé prêt:
/joueurs, flux de connexion, opérations économiques, opérations de garage, opérations d'inventaire, vérifications d'interdiction.
2) Installation et câblage oxmysql
2.1 serveur.cfg
# Arrêter d'utiliser mysql-async default_prio 500 # garantir mysql-async # ← commenter ou supprimer # Démarrer oxmysql default_prio 50 garantir oxmysql # Chaîne de connexion consommée par oxmysql définir mysql_connection_string "mysql://user:pass@127.0.0.1:3306/yourdb?charset=utf8mb4" # Diagnostics facultatifs définir mysql_slow_query_warning 200 # consigner les requêtes plus lentes que 200 ms définir mysql_debug false # vrai pour la journalisation détaillée pendant la préparation
Garder
mysql-asyncdésactivé mais disponible dans votre dossier de ressources pendant la phase de préparation (pour une restauration rapide).
3) Mappage d'API : mysql‑async → oxmysql
mysql-async (héritage):
- Asynchrone :
MySQL.Async.fetchAll,MySQL.Async.fetchScalar,MySQL.Async.execute - Synchronisation :
MySQL.Sync.fetchAll,MySQL.Sync.fetchScalar,MySQL.Sync.execute - Paramètres:
@paramtables de style comme{ ['@identifier']=identifiant }
oxmysql (moderne):
- Style de rappel via exporter:
exports.oxmysql:requête|scalaire|unique|insertion|mise à jour(sql, params, cb) - Promesse/attente via mondial:
MySQL.query|scalaire|unique|insertion|mise à jour.await(sql, params)et les rappels non attendus sans.attendre - Paramètres: positionnel
?via un tableau, ou nommé:nomvia l'objet
3.1 Remplacements courants
SÉLECTIONNEZ plusieurs
-- mysql-async MySQL.Async.fetchAll( 'SELECT * FROM utilisateurs WHERE identifiant = @id', { ['@id'] = identifiant }, function(rows) ... end ) -- oxmysql (rappel via export) exports.oxmysql:query( 'SELECT * FROM utilisateurs WHERE identifiant = ?', { identifiant }, function(rows) ... end ) -- oxmysql (await) local rows = MySQL.query.await( 'SELECT * FROM utilisateurs WHERE identifiant = ?', { identifiant } )
SÉLECTIONNER une seule ligne
-- mysql-async (fetchAll + rows[1]) -- oxmysql local row = MySQL.single.await( 'SELECT * FROM utilisateurs WHERE identifiant = ?', { identifiant } )
SELECT scalaire (par exemple, compter, identifier)
-- mysql-async -- oxmysql local count = MySQL.scalar.await( 'SELECT COUNT(*) FROM owned_vehicles WHERE owner = ?', { owner } )
INSÉRER (obtenir l'insertId)
-- mysql-async (exécuter) -- oxmysql local insertId = MySQL.insert.await( 'INSÉRER DANS notes (propriétaire, texte) VALEURS (?, ?)', { cid, texte } )
MISE À JOUR/SUPPRESSION (affectedRows)
-- mysql-async (exécuter) -- oxmysql local changed = MySQL.update.await( 'UPDATE utilisateurs SET job = ?, job_grade = ? WHERE identifier = ?', { job, grade, identifier } )
Transactions (manuel)
-- 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
Certains frameworks exposent des wrappers (par exemple,
ox_lib) qui ajoutentMySQL.ready,.transaction, etc. Les appels ci-dessus sont sûrs sans wrappers supplémentaires.
4) Aide-mémoire pour les déclarations préparées
Styles de paramètres
- mysql-async (hérité) :
@nomespaces réservés avec un tableau :{ ['@name']=valeur } - oxmysql (positionnel) :
?espaces réservés avec un tableau:{ valeur1, valeur2 } - oxmysql (nommé) :
:nomespaces réservés avec un objet:{ nom = valeur }
Exemples
-- Paramètres nommés (recommandés pour la lisibilité) local row = MySQL.single.await( 'SELECT * FROM users WHERE identifier = :id', { id = identifier } ) -- IN (...) list -- Construisez des espaces réservés dynamiquement et passez un tableau plat local ids = { 'cid1','cid2','cid3' } local qs = ('?,' ):rep(#ids):sub(1,-2) -- "?, ?, ?" lignes locales = MySQL.query.await('SELECT * FROM players WHERE citizenid IN ('..qs..')', ids) -- Champs JSON (MySQL 5.7+/MariaDB 10.2+) nom local = MySQL.scalar.await('SELECT JSON_UNQUOTE(JSON_EXTRACT(data, "$.name")) FROM players WHERE citizenid = ?', { cid })
Faire
- Utiliser déclarations préparées partout (ne jamais concaténer de chaîne les entrées utilisateur).
- Préférer paramètres nommés pour plus de clarté dans les déclarations complexes.
- Ajouter LIMITE 1 lors de la lecture d'une seule entité.
Éviter
- Caractère générique
SÉLECTIONNER *dans les chemins chauds (colonnes nécessaires au projet). - Requêtes N+1 par ligne ; lot avec
DANS (...).
5) Scripts de migration de base de données « UP » (prêts à l'emploi)
Choisissez les blocs correspondant à votre infrastructure (ESX/QBCore) et à votre serveur (MySQL 8+ ou MariaDB 10.4+). Exécutez d'abord en mode staging.
5.1 Normaliser le jeu de caractères et le classement (UTF‑8 partout)
(A) MySQL 8+ - remplacer votredb une fois
-- Forcer la base de données par défaut à utf8mb4 (sûr pour les emojis) ALTER DATABASE `yourdb` CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -- Convertir les tables communes (étendre la liste si nécessaire) 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+ — les mêmes affirmations sont valables.
Ajoutez d'autres tables chaudes (inventaire, facturation, téléphone, société, emplois) telles que présentes sur votre serveur.
5.2 Index ESX (la performance sûre gagne)
MySQL 8+
ALTER TABLE `users` AJOUTER UN INDEX SI N'EXISTE PAS `idx_users_identifier` (`identifier`), AJOUTER UN INDEX SI N'EXISTE PAS `idx_users_job` (`job`), AJOUTER UN INDEX SI N'EXISTE PAS `idx_users_name` (`name`); ALTER TABLE `owned_vehicles` AJOUTER UN INDEX UNIQUE SI N'EXISTE PAS `ux_owned_vehicles_plate` (`plate`), AJOUTER UN INDEX SI N'EXISTE PAS `idx_owned_vehicles_owner` (`owner`);
MariaDB 10.4+
-- Supprimer d'abord pour être idempotent où IF NOT EXISTS n'est pas disponible 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`; CRÉER UN INDEX UNIQUE `ux_owned_vehicles_plate` SUR `owned_vehicles` (`plate`); CRÉER UN INDEX `idx_owned_vehicles_owner` SUR `owned_vehicles` (`owner`);
5.3 Index QBCore/QBOX
MySQL 8+
ALTER TABLE `players` AJOUTER UN INDEX UNIQUE SI `ux_players_citizenid` (`citizenid`) N'EXISTE PAS, AJOUTER UN INDEX SI `idx_players_license` (`license`) N'EXISTE PAS, AJOUTER UN INDEX SI `idx_players_steam` (`steam`) N'EXISTE PAS, AJOUTER UN INDEX SI `idx_players_last_name` (`lastname`) N'EXISTE PAS; ALTER TABLE `player_vehicles` AJOUTER UN INDEX UNIQUE SI `ux_player_vehicles_plate` (`plate`) N'EXISTE PAS, AJOUTER UN INDEX SI `idx_player_vehicles_citizenid` (`citizenid`) N'EXISTE PAS;
MariaDB 10.4+
SUPPRIMER L'INDEX SI EXISTE `ux_players_citizenid` SUR `players`; SUPPRIMER L'INDEX SI EXISTE `idx_players_license` SUR `players`; SUPPRIMER L'INDEX SI EXISTE `idx_players_steam` SUR `players`; SUPPRIMER L'INDEX SI EXISTE `idx_players_last_name` SUR `players`; CRÉER UN INDEX UNIQUE `ux_players_citizenid` SUR `players` (`citizenid`); CRÉER L'INDEX `idx_players_license` SUR `players` (`license`); CRÉER L'INDEX `idx_players_steam` SUR `players` (`steam`); CRÉER L'INDEX `idx_players_last_name` SUR `players` (`lastname`); SUPPRIMER L'INDEX SI EXISTE `ux_player_vehicles_plate` SUR `player_vehicles`; SUPPRIMER L'INDEX SI EXISTE `idx_player_vehicles_citizenid` SUR `player_vehicles`; CRÉER UN INDEX UNIQUE `ux_player_vehicles_plate` SUR `player_vehicles` (`plate`); CRÉER L'INDEX `idx_player_vehicles_citizenid` SUR `player_vehicles` (`citizenid`);
5.4 Facultatif : ox_inventory (si installé)
ALTER TABLE `ox_inventory` AJOUTER UN INDEX SI `idx_inv_owner` (`owner`), AJOUTER UN INDEX SI `idx_inv_type` (`type`); ALTER TABLE `ox_inventory_items` AJOUTER UN INDEX SI `idx_items_inv_owner_name` (`inventory`, `owner`, `name`);
Ajustez les noms de table si votre schéma diffère (certaines configurations utilisent
stocks/articles).
6) Plan de retour en arrière (zéro panique)
6.1 Restauration du code
- Annulez vos modifications de ressources (conservez un
héritage-mysql-asyncbifurquer). - Dans
serveur.cfgéchanger:# assure oxmysql assure mysql-async - Redémarrez FXServer ou les ressources affectées dans l’ordre de dépendance.
6.2 Restauration SQL
- Si seulement tu index ajoutés: laissez-les tomber (voir le MariaDB blocs ci-dessus — utiliser
SUPPRIMER L'INDEX S'IL EXISTE). - Si tu jeu de caractères/classement modifié et doit annuler, rétablir la base de données et les tables :
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;
Préfère restaurer à partir de sauvegarde.sql au lieu d'inversions massives de jeux de caractères lorsque cela est possible.
7) Procédure de migration de bout en bout (scriptable)
- Déploiements gelés, sauvegarder la base de données.
- Appliquer Section 5 « UP » SQL en phase → vérifier → prod.
- Refactorisation du code de validation : remplacement des appels (section 3) + styles de paramètres (section 4).
- Déployer,
assurer oxmysql, redémarrez le serveur. - Courir tests de fumée (connexion, chèques de paie, inventaire, apparition/désapparition de véhicules, interdictions, argent de la société, basculements des tâches).
- Regardez les journaux pendant 15 à 30 minutes (
avertissement de requête lente mysql(aide) ; corrigez les paramètres manquants ou les incompatibilités de schéma.
8) Micro-benchmarks (Apportez vos propres chiffres)
Une petite ressource que vous pouvez utiliser pour comparer les requêtes hot-path sur ton matériel et ensemble de données.
fxmanifest.lua
fx_version 'cerulean' jeu 'gta5' script_serveur '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)
Comment courir
- Placez la ressource dans un dossier (par exemple,
banc à bœufs/), ajouterassurer le banc à bœufsàserveur.cfg. - Console du serveur Tail ; les résultats s'impriment sous forme de lignes comme :
[bench] SELECT simple : 134,21 ms. - Pour un avant/après comparaison, exécuter une fois avec
mysql-async(ajuster les appels si nécessaire), puis avecoxmysql.
Que rechercher
- Nombre total de ms inférieur par section après la migration.
- Latence P95/P99 inférieure sur les actions de jeu liées aux requêtes.
- Moins d’avertissements de requête lente sur une heure de lecture en direct.
9) Dépannage
Q : J'obtiens « aucune exportation de ce type : requête/single/… ».
UN: oxmysql n'est pas commencé assez tôt. Assurez-vous assurer oxmysql est au-dessus des ressources qui l'utilisent.
Q : Erreurs de paramètres ou résultats vides.
A : Vous avez probablement gardé @param espaces réservés. Remplacer par ? ou :nom et passez un tableau/objet en conséquence.
Q : Blocages ou écritures partielles.
A : Enveloppez les soldes/transferts à plusieurs étapes dans une transaction (voir la section 3), ajoutez les index de la section 5.
Q : Le chemin JSON renvoie NULL.
A : Confirmez que votre moteur prend en charge les fonctions JSON (MySQL ≥5.7/MariaDB ≥10.2) et que le type de colonne est JSON (pas TEXTE LONG).
Q : Lent après la migration.
A : Vérifier les index manquants, EXPLIQUER votre requête, projetez uniquement les colonnes nécessaires et consultez le manuel d'optimisation du serveur.
10) Liste de contrôle de révision du code (copier/coller)
- Aucune chaîne SQL concaténée ; toutes les requêtes sont paramétrées.
- Utiliser
.célibataire/.scalaireavecLIMITE 1lorsqu'une seule ligne/valeur est requise. - Lot
DANS (...)lectures pour collections. - Transactions autour d’opérations monétaires/d’inventaire en plusieurs étapes.
- Index présent pour chaque chaud
OÙ/REJOINDREcolonne. - Éviter
SÉLECTIONNER *dans les sentiers chauds. - Enregistrez les requêtes lentes ; suivez les principaux contrevenants chaque semaine.
Liens internes
- Optimisation du serveur FiveM : le guide ultime pour 2025 — https://fivemx.com/fivem-server-optimization/
- Modèles d'adaptateur : Exportations, événements et modèles de lecteur ESX↔QBCore↔QBOX — https://fivemx.com/adapter-patterns/
Crédits
Maintenu par fivemx.com. Les contributions sont les bienvenues (envoyez des différences d'index sécurisés supplémentaires ou d'aides wrapper).






