Économisez 20% dès aujourd'hui Utilisez le code BIENVENUE lors du paiement. ACCUEILLIR

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 :


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

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

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

Luc
Luc

Je m'appelle Luke, je suis un joueur et j'adore écrire sur FiveM, GTA et le jeu de rôle. Je dirige une communauté de jeu de rôle et j'ai environ 10 ans d'expérience dans l'administration de serveurs.

Articles: 570