Économisez 20 % avec WELCOMEVoir les promotions
mysql asynchrone oxmysql

De mysql-async à oxmysql : Migration et requêtes sécurisées…

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 :


TL;DR

  • Utiliser oxmysql: instructions préparées, API promise/wait, meilleurs diagnostics, performances élevées.
  • Modifications minimales du code: échanger @param? (positionnel) ou :nom paramètres (nommés) ; remplacer MySQL.Async.* appels avec MySQL.*/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

  1. Sauvegarde complète: mysqldump --single-transaction votredb > backup.sql.
  2. Environnement de mise en scène mise en miroir du schéma de production + sous-ensemble de données.
  3. Artefact et dépendances: Version actuelle de FXServer, la plus récente oxmysql.
  4. Fenêtre d'arrêt pour le changement de produit (généralement < 5 minutes).
  5. 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êtez d'utiliser mysql-async
default_prio 500
# assure mysql-async # ← commenter ou supprimer

# Démarrer oxmysql
default_prio 50
assure oxmysql

# Chaîne de connexion consommée par oxmysql
set mysql_connection_string "mysql://user:pass@127.0.0.1:3306/yourdb?charset=utf8mb4"

# Diagnostics optionnels
set mysql_slow_query_warning 200 # journaliser les requêtes plus lentes que 200ms
set mysql_debug false # true pour un journalisation verbos pendant le staging

Garder mysql-async dé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: @param tables 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é :nom via l'objet

3.1 Remplacements courants

SÉLECTIONNEZ plusieurs

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

SÉLECTIONNER une seule ligne

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

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

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 (execute)

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

MISE À JOUR/SUPPRESSION (affectedRows)

-- mysql-async (execute)

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

Transactions (manuel)

-- oxmysql transaction manuelle
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 ajoutent MySQL.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é) : @nom espaces réservés avec un tableau : { ['@name']=valeur }
  • oxmysql (positionnel) : ? espaces réservés avec un tableau: { valeur1, valeur2 }
  • oxmysql (nommé) : :nom espaces 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 }
)

-- Liste IN (...)
-- Construire des espaces réservés de manière dynamique et passer un tableau plat
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)

-- Champs JSON (MySQL 5.7+/MariaDB 10.2+)
local name = 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`
 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+

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

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

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

  1. Annulez vos modifications de ressources (conservez un héritage-mysql-async bifurquer).
  2. Dans serveur.cfg échanger: # assure oxmysql assure mysql-async
  3. 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)

  1. Déploiements gelés, sauvegarder la base de données.
  2. Appliquer Section 5 « UP » SQL en phase → vérifier → prod.
  3. Refactorisation du code de validation : remplacement des appels (section 3) + styles de paramètres (section 4).
  4. Déployer, assurer oxmysql, redémarrez le serveur.
  5. 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).
  6. 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 -- ajustez pour votre serveur

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: recherche de propriété
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: récupération par lots
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: mise à jour avec garde
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)

Comment courir

  1. Placez la ressource dans un dossier (par exemple, banc à bœufs/), ajouter assurer le banc à bœufs à serveur.cfg.
  2. Console du serveur Tail ; les résultats s'impriment sous forme de lignes comme : [bench] SELECT simple : 134,21 ms.
  3. Pour un avant/après comparaison, exécuter une fois avec mysql-async (ajuster les appels si nécessaire), puis avec oxmysql.

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/.scalaire avec LIMITE 1 lorsqu'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 /REJOINDRE colonne.
  • Éviter SÉLECTIONNER * dans les sentiers chauds.
  • Enregistrez les requêtes lentes ; suivez les principaux contrevenants chaque semaine.

Liens internes


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