Publiek: FiveM server-eigenaren, scripters, beheerders
Doel: Vervangen mysql-async met oxmysql veilig, versnel query's en moderniseer je SQL-gebruik.
Lees ook:
- FiveM Serveroptimalisatie: Het Definitieve Handboek 2025 — https://fivemx.com/how-to-optimize-fivem-server-performance/
- Adapterpatronen: ESX↔QBCore↔QBOX Exports, Events & Player Models — https://fivemx.com/adapter-patterns/
TL;DR
- Gebruik
oxmysql: voorbereide statements, promise/await API, betere diagnostiek, sterke prestaties. - Minimale codeaanpassingen: vervang
@param→?(positioneel) of:name(benoemde) parameters; vervangMySQL.Async.*aanroepen doorMySQL.*/exports.oxmysql:*. - Voer de SQL “UP”-scripts uit hieronder (charset/index-fixes) en houd de rollback bij de hand.
- Verifieer met de micro-benchmark-opstelling aan het einde om winst op jouw hardware te bevestigen.
1) Veiligheidscheck voor vertrek
- Volledige back-up:
mysqldump --single-transaction yourdb > backup.sql. - Staging-omgeving die de productieschema + gegevenssubset weerspiegelt.
- Artifact & afhankelijkheden: Huidige FXServer build, nieuwste
oxmysql. - Downtime-venster voor productieomschakeling (meestal < 5 minuten).
- Gezondheidscontroles gereed:
/players, inlogstroom, economie-operaties, garage-operaties, inventaris-operaties, bancontroles.
2) Installeren & Aansluiten oxmysql
2.1 server.cfg
# Stop using mysql-async default_prio 500 # ensure mysql-async # ← comment out or remove # Start oxmysql default_prio 50 ensure oxmysql # Connection string consumed by oxmysql set mysql_connection_string "mysql://user:pass@127.0.0.1:3306/yourdb?charset=utf8mb4" # Optional diagnostics set mysql_slow_query_warning 200 # log queries slower than 200ms set mysql_debug false # true for verbose logging during staging
Houden
mysql-asyncuitgeschakeld maar beschikbaar in je resources-map tijdens de stagingfase (voor snelle terugdraaiing).
3) API Mapping: mysql‑async → oxmysql
mysql-async (verouderd):
- Async:
MySQL.Async.fetchAll,MySQL.Async.fetchScalar,MySQL.Async.execute - Sync:
MySQL.Sync.fetchAll,MySQL.Sync.fetchScalar,MySQL.Sync.execute - Parameters:
@paramtabellen opmaken zoals{ ['@identifier']=identifier }
oxmysql (modern):
- Callback-stijl via export:
exports.oxmysql:query|scalar|single|insert|update(sql, params, cb) - Promise/await via global:
MySQL.query|scalar|single|insert|update.await(sql, params)en non-await callbacks zonder.await - Parameters: positioneel
?via array, of genaamd:namevia object
3.1 Veelvoorkomende vervangingen
SELECT meerdere
-- 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 }
)
SELECT enkele rij
-- mysql-async (fetchAll + rows[1])
-- oxmysql
local row = MySQL.single.await(
'SELECT * FROM users WHERE identifier = ?',
{ identifier }
)
SELECT scalar (bijv., count, id)
-- mysql-async
-- oxmysql
local count = MySQL.scalar.await(
'SELECT COUNT(*) FROM owned_vehicles WHERE owner = ?',
{ owner }
)
INVOEGEN (insertId ophalen)
-- mysql-async (execute)
-- oxmysql
local insertId = MySQL.insert.await(
'INSERT INTO notes (owner, text) VALUES (?, ?)',
{ cid, text }
)
UPDATE/DELETE (affectedRows)
-- mysql-async (execute)
-- oxmysql
local changed = MySQL.update.await(
'UPDATE users SET job = ?, job_grade = ? WHERE identifier = ?',
{ job, grade, identifier }
)
Transacties (handmatig)
-- 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
Sommige frameworks bieden wrappers aan (bijv.,
ox_lib) die toevoegenMySQL.ready,.transaction, etc. De bovenstaande aanroepen zijn veilig zonder extra wrappers.
4) Cheat‑Sheet voor voorbereide statements
Param-stijlen
- mysql-async (legacy):
@nameplaceholders met een tabel:{ ['@name']=value } - oxmysql (positioneel):
?placeholders met een array:{ value1, value2 } - oxmysql (genoemd):
:nameplaceholders met een object:{ name = value }
Voorbeelden
-- Named params (recommended for readability)
local row = MySQL.single.await(
'SELECT * FROM users WHERE identifier = :id',
{ id = identifier }
)
-- IN (...) list
-- Build placeholders dynamically and pass a flat array
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)
-- JSON fields (MySQL 5.7+/MariaDB 10.2+)
local name = MySQL.scalar.await('SELECT JSON_UNQUOTE(JSON_EXTRACT(data, "$.name")) FROM players WHERE citizenid = ?', { cid })
Doe
- Gebruik prepared statements overal (gebruik nooit string-concatenatie van gebruikersinvoer).
- Geef de voorkeur aan genormaliseerde parameters voor duidelijkheid in complexe statements.
- Toevoegen LIMIT 1 bij het lezen van een enkele entiteit.
Vermijd
- Wildcard
SELECT *in hot paths (projecteer benodigde kolommen). - N+1 queries per rij; batch met
IN (...).
5) Database “UP” Migratiescripts (Klaar om te draaien)
Kies de blokken die bij jouw framework (ESX/QBCore) en server (MySQL 8+ of MariaDB 10.4+) passen. Test eerst op staging.
5.1 Normaliseer Tekenset & Collator (UTF‑8 overal)
(A) MySQL 8+ — vervangen yourdb eenmalig
-- Force database default to utf8mb4 (emoji‑safe) ALTER DATABASE `yourdb` CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -- Convert common tables (extend list as needed) 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+ — dezelfde uitspraken zijn geldig.
Voeg andere veelgebruikte tabellen toe (inventory, billing, phone, society, jobs) zoals aanwezig op jouw server.
5.2 ESX Indexen (veilige prestatiewinst)
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+
-- Drop first to be idempotent where IF NOT EXISTS is unavailable 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`; CREATE UNIQUE INDEX `ux_owned_vehicles_plate` ON `owned_vehicles` (`plate`); CREATE INDEX `idx_owned_vehicles_owner` ON `owned_vehicles` (`owner`);
5.3 QBCore/QBOX Indexen
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+
DROP INDEX IF EXISTS `ux_players_citizenid` ON `players`; DROP INDEX IF EXISTS `idx_players_license` ON `players`; DROP INDEX IF EXISTS `idx_players_steam` ON `players`; DROP INDEX IF EXISTS `idx_players_last_name` ON `players`; CREATE UNIQUE INDEX `ux_players_citizenid` ON `players` (`citizenid`); CREATE INDEX `idx_players_license` ON `players` (`license`); CREATE INDEX `idx_players_steam` ON `players` (`steam`); CREATE INDEX `idx_players_last_name` ON `players` (`lastname`); DROP INDEX IF EXISTS `ux_player_vehicles_plate` ON `player_vehicles`; DROP INDEX IF EXISTS `idx_player_vehicles_citizenid` ON `player_vehicles`; CREATE UNIQUE INDEX `ux_player_vehicles_plate` ON `player_vehicles` (`plate`); CREATE INDEX `idx_player_vehicles_citizenid` ON `player_vehicles` (`citizenid`);
5.4 Optioneel: ox_inventory (indien geïnstalleerd)
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`);
Pas tabelnamen aan als uw schema verschilt (sommige installaties gebruiken
inventories/items).
6) Rollbackplan (Zero‑Panic)
6.1 Code rollback
- Keer je resource-wijzigingen terug (bewaar een
legacy-mysql-asyncbranch). - In
server.cfgwissel:# ensure oxmysql ensure mysql-async - Herstart FXServer of de getroffen resources in afhankelijkheidsvolgorde.
6.2 SQL rollback
- Als je alleen toegevoegde indexen: verwijder ze (zie de MariaDB blocks hierboven — gebruik
DROP INDEX IF EXISTS). - Als je veranderde charset/collation en moet ongedaan maken, de DB en tabellen terugdraaien:
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;
Herstel bij voorkeur vanuit backup.sql in plaats van massale charset-omkeringen waar mogelijk.
7) End-to-End Migratieprocedure (Scriptbaar)
- Bevries deploys, maak een back-up van de DB.
- Toepassen Sectie 5 “UP” SQL op staging → verifiëren → prod.
- Commit code refactor: vervang aanroepen (Sectie 3) + parameterstijlen (Sectie 4).
- Implementeren,
ensure oxmysql, start de server opnieuw op. - Voer smoke tests (login, salarisstrookjes, inventaris, voertuig spawnen/despawnen, verbanningen, maatschappijgeld, job duty schakelaars).
- Bekijk logs gedurende 15-30 minuten (
mysql_slow_query_warninghelpt); los eventuele gemiste parameters of schema-mismatches op.
8) Microbenchmarks (Breng je eigen cijfers mee)
Een kleine resource die je kunt toevoegen om hot-path queries te vergelijken jouw hardware en dataset.
fxmanifest.lua
fx_version 'cerulean' game '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)
Hoe te draaien
- Plaats de resource in een map (bijv.
ox-bench/), toevoegenensure ox-benchnaarserver.cfg. - Bekijk de server console; resultaten worden als regels weergegeven zoals:
[bench] SELECT single: 134.21 ms. - Voor een voor/na ter vergelijking, voer een keer uit met
mysql-async(pas de aanroepen indien nodig aan), dan metoxmysql.
Waar je op moet letten
- Lagere totale ms per sectie na migratie.
- Lagere P95/P99-latentie bij gameplay-acties die aan queries zijn gekoppeld.
- Minder slow‑query waarschuwingen gedurende een uur live spelen.
9) Problemen oplossen
V: Ik krijg “no such export: query/single/…”.
A: oxmysql niet vroeg genoeg wordt gestart. Zorg ervoor dat ensure oxmysql staat boven bronnen die het gebruiken.
V: Parameterfouten of lege resultaten.
A: Je hebt waarschijnlijk @param placeholders. Vervang door ? of :name en geef dienovereenkomstig een array/object door.
V: Deadlocks of gedeeltelijke schrijfbewerkingen.
A: Wikkel multi-step saldi/overdrachten in een transactie (zie Sectie 3), voeg indexen toe uit Sectie 5.
V: JSON-pad retourneert NULL.
A: Bevestig dat je engine JSON-functies ondersteunt (MySQL ≥5.7/MariaDB ≥10.2) en dat het kolomtype is JSON (niet LONGTEXT).
V: Traag na migratie.
A: Controleer ontbrekende indexes, EXPLAIN jouw query, projecteer alleen benodigde kolommen en raadpleeg het Server Optimalisatie Draaiboek.
10) Code Review Checklist (kopiëren/plakken)
- Geen string‑concatenatie SQL; alle queries geparameteriseerd.
- Gebruik
.single/.scalarmetLIMIT 1wanneer slechts één rij/waarde vereist is. - Batch
IN (...)leest voor collecties. - Transacties rond meerstaps geld/inventory-operaties.
- Index aanwezig voor elke hot
WHERE/JOINkolom. - Vermijd
SELECT *in hot paths. - Log langzame queries; volg wekelijks de grootste overtreders.
Interne Links
- FiveM Serveroptimalisatie: Het Definitieve Handboek 2025 — https://fivemx.com/how-to-optimize-fivem-server-performance/
- Adapterpatronen: ESX↔QBCore↔QBOX Exports, Events & Player Models — https://fivemx.com/adapter-patterns/
Credits
Onderhouden door fivemx.com. Bijdragen welkom (stuur diffs van extra veilige indexen of wrapper-helpers).
