De mysql-async para oxmysql: Migração segura e consultas seguras…
Público: Proprietários, criadores de scripts e mantenedores de servidores FiveM
Meta: Substituir mysql-assíncrono com oxmysql com segurança, acelere consultas e modernize seu uso de SQL.
Leia também:
- Otimização de Servidores FiveM: O Manual Definitivo para 2025 — https://fivemx.com/fivem-server-optimization/
- Padrões de Adaptadores: Exportações ESX↔QBCore↔QBOX, Eventos e Modelos de Player — https://fivemx.com/adapter-patterns/
Resumo
- Usar
oxmysql: instruções preparadas, API promise/await, melhor diagnóstico, desempenho forte. - Mudanças mínimas no código: trocar
@param→?(posicional) ou:nomeparâmetros (nomeados); substituirMySQL.Async.*chamadas comMySQL.*/exportações.oxmysql:*. - Execute os scripts SQL “UP” abaixo (correções de conjunto de caracteres/índice) e manter o reversão útil.
- Verifique com o chicote de micro-benchmark no final para confirmar as vitórias no seu hardware.
1) Lista de verificação de segurança pré-voo
- Backup completo:
mysqldump --single-transaction yourdb > backup.sql. - Ambiente de preparação espelhamento de esquema de produção + subconjunto de dados.
- Artefato e dependências: Build atual do FXServer, mais recente
oxmysql. - Janela de tempo de inatividade para troca de produto (geralmente < 5 minutos).
- Sondas de saúde preparar:
/jogadores, fluxo de login, operações de economia, operações de garagem, operações de inventário, verificações de banimento.
2) Instalação e fiação oxmysql
2.1 servidor.cfg
# Pare de usar mysql-async default_prio 500 # garanta mysql-async # ← comente ou remova # Inicie oxmysql default_prio 50 garanta oxmysql # String de conexão consumida por oxmysql defina mysql_connection_string "mysql://user:pass@127.0.0.1:3306/yourdb?charset=utf8mb4" # Diagnósticos opcionais defina mysql_slow_query_warning 200 # consultas de log mais lentas que 200 ms defina mysql_debug false # true para log detalhado durante o preparo
Manter
mysql-assíncronodesabilitado, mas disponível na sua pasta de recursos durante a fase de preparação (para reversão rápida).
3) Mapeamento de API: mysql‑async → oxmysql
mysql-assíncrono (legado):
- Assíncrono:
MySQL.Async.fetchAll,MySQL.Async.fetchScalar,MySQL.Async.execute - Sincronizar:
MySQL.Sync.fetchAll,MySQL.Sync.fetchScalar,MySQL.Sync.execute - Parâmetros:
@paramtabelas de estilo como{ ['@identifier']=identificador }
oxmysql (moderno):
- Estilo de retorno de chamada via exportar:
exports.oxmysql:consulta|escalar|único|inserir|atualizar(sql, parâmetros, cb) - Prometer/aguardar via global:
MySQL.query|scalar|single|insert|update.await(sql, parâmetros)e retornos de chamada não aguardados sem.aguardar - Parâmetros: posicional
?via array, ou nomeado:nomevia objeto
3.1 Substituições comuns
SELECIONE muitos
-- mysql-async MySQL.Async.fetchAll( 'SELECT * FROM usuários ONDE identificador = @id', { ['@id'] = identificador }, função(linhas) ... fim ) -- oxmysql (retorno de chamada via exportação) exports.oxmysql:query( 'SELECT * FROM usuários ONDE identificador = ?', { identificador }, função(linhas) ... fim ) -- oxmysql (aguardar) linhas locais = MySQL.query.await( 'SELECT * FROM usuários ONDE identificador = ?', { identificador } )
SELECIONE uma única linha
-- mysql-async (fetchAll + rows[1]) -- oxmysql local row = MySQL.single.await( 'SELECT * FROM usuários ONDE identificador = ?', { identificador } )
SELECIONE escalar (por exemplo, contagem, id)
-- mysql-async -- oxmysql contagem local = MySQL.scalar.await( 'SELECIONE CONTAGEM(*) DE veículos_próprios ONDE proprietário = ?', { proprietário } )
INSERIR (obter insertId)
-- mysql-async (executar) -- oxmysql local insertId = MySQL.insert.await( 'INSERIR EM notas (proprietário, texto) VALORES (?, ?)', { cid, texto } )
ATUALIZAR/EXCLUIR (linhas afetadas)
-- mysql-async (executar) -- oxmysql local changed = MySQL.update.await( 'ATUALIZAR usuários DEFINIR job = ?, job_grade = ? ONDE identificador = ?', { job, grade, identificador } )
Transações (manual)
-- 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
Algumas estruturas expõem wrappers (por exemplo,
boi_lib) que adicionamMySQL.pronto,.transação, etc. As chamadas acima são seguras sem wrappers extras.
4) Folha de dicas de declarações preparadas
Estilos de parâmetros
- mysql‑async (legado):
@nomemarcadores de posição com uma tabela:{ ['@nome']=valor } - oxmysql (posicional):
?marcadores de posição com um variedade:{ valor1, valor2 } - oxmysql (nomeado):
:nomemarcadores de posição com um objeto:{ nome = valor }
Exemplos
-- Parâmetros nomeados (recomendado para legibilidade) local row = MySQL.single.await( 'SELECT * FROM users WHERE identifier = :id', { id = identifier } ) -- IN (...) list -- Crie espaços reservados dinamicamente e passe um array simples local ids = { 'cid1','cid2','cid3' } local qs = ('?,' ):rep(#ids):sub(1,-2) -- "?, ?, ?" linhas locais = MySQL.query.await('SELECT * FROM jogadores ONDE citizenid IN ('..qs..')', ids) -- campos JSON (MySQL 5.7+/MariaDB 10.2+) nome local = MySQL.scalar.await('SELECT JSON_UNQUOTE(JSON_EXTRACT(dados, "$.nome")) DE jogadores ONDE citizenid = ?', { cid })
Fazer
- Usar declarações preparadas em todos os lugares (nunca concatene strings de entrada do usuário).
- Prefer parâmetros nomeados para maior clareza em declarações complexas.
- Adicionar LIMITE 1 ao ler uma única entidade.
Evitar
- Curinga
SELECIONE *em caminhos ativos (colunas necessárias para o projeto). - Consultas N+1 por linha; lote com
EM (...).
5) Scripts de migração de banco de dados “UP” (prontos para execução)
Selecione os blocos que correspondem à sua estrutura (ESX/QBCore) e servidor (MySQL 8+ ou MariaDB 10.4+). Execute primeiro no staging.
5.1 Normalizar conjunto de caracteres e agrupamento (UTF‑8 em todos os lugares)
(A) MySQL 8+ - substituir seu banco de dados uma vez
-- Força o padrão do banco de dados para utf8mb4 (seguro para emojis) ALTER DATABASE `yourdb` CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -- Converte tabelas comuns (estende a lista conforme necessário) 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+ — as mesmas afirmações são válidas.
Adicione outras tabelas ativas (inventário, faturamento, telefone, sociedade, trabalhos) presentes no seu servidor.
5.2 Índices ESX (ganhos de desempenho seguro)
MySQL 8+
ALTER TABLE `users` ADD INDEX SE NÃO EXISTIR `idx_users_identifier` (`identifier`), ADD INDEX SE NÃO EXISTIR `idx_users_job` (`job`), ADD INDEX SE NÃO EXISTIR `idx_users_name` (`name`); ALTER TABLE `owned_vehicles` ADD INDEX ÚNICO SE NÃO EXISTIR `ux_owned_vehicles_plate` (`plate`), ADD INDEX SE NÃO EXISTIR `idx_owned_vehicles_owner` (`owner`);
MariaDB 10.4+
-- Solte primeiro para ser idempotente onde SE NÃO EXISTIR não estiver disponível. SOLTE O ÍNDICE SE EXISTIR `idx_users_identifier` EM `users`; SOLTE O ÍNDICE SE EXISTIR `idx_users_job` EM `users`; SOLTE O ÍNDICE SE EXISTIR `idx_users_name` EM `users`; CRIAR O ÍNDICE `idx_users_identifier` EM `users` (`identifier`); CRIAR O ÍNDICE `idx_users_job` EM `users` (`job`); CRIAR O ÍNDICE `idx_users_name` EM `users` (`name`); SOLTE O ÍNDICE SE EXISTIR `ux_owned_vehicles_plate` EM `owned_vehicles`; SOLTE O ÍNDICE SE EXISTIR `idx_owned_vehicles_owner` EM `owned_vehicles`; CRIAR ÍNDICE ÚNICO `ux_owned_vehicles_plate` EM `owned_vehicles` (`plate`); CRIAR ÍNDICE `idx_owned_vehicles_owner` EM `owned_vehicles` (`owner`);
5.3 Índices QBCore/QBOX
MySQL 8+
ALTER TABLE `players` ADICIONE ÍNDICE ÚNICO SE NÃO EXISTIR `ux_players_citizenid` (`citizenid`), ADICIONE ÍNDICE SE NÃO EXISTIR `idx_players_license` (`license`), ADICIONE ÍNDICE SE NÃO EXISTIR `idx_players_steam` (`steam`), ADICIONE ÍNDICE SE NÃO EXISTIR `idx_players_last_name` (`lastname`); ALTER TABLE `player_vehicles` ADICIONE ÍNDICE ÚNICO SE NÃO EXISTIR `ux_player_vehicles_plate` (`plate`), ADICIONE ÍNDICE SE NÃO EXISTIR `idx_player_vehicles_citizenid` (`citizenid`);
MariaDB 10.4+
REMOVER ÍNDICE SE EXISTIR `ux_players_citizenid` EM `players`; REMOVER ÍNDICE SE EXISTIR `idx_players_license` EM `players`; REMOVER ÍNDICE SE EXISTIR `idx_players_steam` EM `players`; REMOVER ÍNDICE SE EXISTIR `idx_players_last_name` EM `players`; CRIAR ÍNDICE ÚNICO `ux_players_citizenid` EM `players` (`citizenid`); CRIAR ÍNDICE `idx_players_license` EM `players` (`license`); CRIAR ÍNDICE `idx_players_steam` EM `players` (`steam`); CRIAR ÍNDICE `idx_players_last_name` EM `players` (`lastname`); REMOVER ÍNDICE SE EXISTIR `ux_player_vehicles_plate` EM `player_vehicles`; REMOVER ÍNDICE SE EXISTIR `idx_player_vehicles_citizenid` EM `player_vehicles`; CRIAR ÍNDICE ÚNICO `ux_player_vehicles_plate` EM `player_vehicles` (`plate`); CRIAR ÍNDICE `idx_player_vehicles_citizenid` EM `player_vehicles` (`citizenid`);
5.4 Opcional: ox_inventory (se instalado)
ALTER TABLE `ox_inventory` ADICIONAR ÍNDICE SE NÃO EXISTIR `idx_inv_owner` (`owner`), ADICIONAR ÍNDICE SE NÃO EXISTIR `idx_inv_type` (`type`); ALTER TABLE `ox_inventory_items` ADICIONAR ÍNDICE SE NÃO EXISTIR `idx_items_inv_owner_name` (`inventory`, `owner`, `name`);
Ajuste os nomes das tabelas se o seu esquema for diferente (algumas configurações usam
estoques/Unid).
6) Plano de Reversão (Zero-Pânico)
6.1 Reversão de código
- Reverta suas alterações de recursos (mantenha um
legado-mysql-asyncfilial). - Em
servidor.cfgtrocar:# garante oxmysql garante mysql-async - Reinicie o FXServer ou os recursos afetados em ordem de dependência.
6.2 Reversão de SQL
- Se você apenas índices adicionados: deixe-os cair (veja o MariaDB blocos acima — use
SOLTE O ÍNDICE SE EXISTIR). - Se você conjunto de caracteres/agrupamento alterado e deve desfazer, reverter o BD e as tabelas:
ALTER DATABASE `yourdb` CONJUNTO DE CARACTERES = utf8 COLLATE = utf8_general_ci; ALTER TABLE `users` CONVERTER PARA CONJUNTO DE CARACTERES utf8 COLLATE utf8_general_ci; ALTER TABLE `owned_vehicles` CONVERTER PARA CONJUNTO DE CARACTERES utf8 COLLATE utf8_general_ci; ALTER TABLE `players` CONVERTER PARA CONJUNTO DE CARACTERES utf8 COLLATE utf8_general_ci; ALTER TABLE `player_vehicles` CONVERTER PARA CONJUNTO DE CARACTERES utf8 COLLATE utf8_general_ci;
Prefira restaurar de backup.sql em vez de inversões em massa de conjuntos de caracteres quando possível.
7) Procedimento de migração de ponta a ponta (programável)
- Congelar implantações, faça backup do banco de dados.
- Aplicar Seção 5 “UP” SQL em preparação → verificar → produção.
- Refatoração de código de confirmação: substituição de chamadas (Seção 3) + estilos de parâmetros (Seção 4).
- Implantar,
garantir oxmysql, reinicie o servidor. - Correr testes de fumaça (login, contracheques, inventário, geração/desaparecimento de veículos, banimentos, dinheiro da sociedade, alternância de funções).
- Observe os registros por 15 a 30 minutos (
aviso_de_consulta_lenta_do_mysqlajuda); corrija quaisquer parâmetros perdidos ou incompatibilidades de esquema.
8) Micro-Benchmarks (Traga seus próprios números)
Um pequeno recurso que você pode usar para comparar consultas de caminho ativo em seu hardware e conjunto de dados.
fxmanifest.lua
fx_version 'cerúleo' jogo 'gta5' server_script 'bench.lua'
banco.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)
Como correr
- Coloque o recurso em uma pasta (por exemplo,
banco de bois/), adicionargarantir banco de boisparaservidor.cfg. - Console do servidor Tail; os resultados são impressos como linhas como:
[banco] SELECIONE único: 134,21 ms. - Para um antes/depois comparação, execute uma vez com
mysql-assíncrono(ajuste as chamadas se necessário), então comoxmysql.
O que procurar
- Menor total de ms por seção após a migração.
- Menor latência P95/P99 em ações de jogo vinculadas a consultas.
- Menos avisos de consulta lenta durante uma hora de reprodução ao vivo.
9) Solução de problemas
P: Recebo “nenhuma exportação: consulta/única/…”.
UM: oxmysql não foi iniciado cedo o suficiente. Certifique-se garantir oxmysql está acima dos recursos que o utilizam.
P: Erros de parâmetros ou resultados vazios.
A: Você provavelmente manteve @param espaços reservados. Substitua por ? ou :nome e passe um array/objeto adequadamente.
P: Deadlocks ou gravações parciais.
R: Envolva saldos/transferências de várias etapas em uma transação (consulte a Seção 3) e adicione índices da Seção 5.
P: O caminho JSON retorna NULL.
R: Confirme se seu mecanismo oferece suporte a funções JSON (MySQL ≥5.7/MariaDB ≥10.2) e se o tipo de coluna é JSON (não TEXTO LONGO).
P: Lento após a migração.
A: Verifique os índices ausentes, EXPLICAR sua consulta, projete apenas as colunas necessárias e revise o Manual de Otimização do Servidor.
10) Lista de verificação de revisão de código (copiar/colar)
- Nenhum SQL concatenado por strings; todas as consultas são parametrizadas.
- Usar
.solteiro/.escalarcomLIMITE 1quando apenas uma linha/valor é necessário. - Lote
EM (...)lê para coleções. - Transações em torno de operações de dinheiro/inventário de várias etapas.
- Índice presente para cada quente
ONDE/JUNTARcoluna. - Evitar
SELECIONE *em caminhos quentes. - Registre consultas lentas e acompanhe os principais infratores semanalmente.
Links internos
- Otimização de Servidores FiveM: O Manual Definitivo para 2025 — https://fivemx.com/fivem-server-optimization/
- Padrões de Adaptadores: Exportações ESX↔QBCore↔QBOX, Eventos e Modelos de Player — https://fivemx.com/adapter-patterns/
Créditos
Mantido por fivemx.com. Contribuições são bem-vindas (envie diffs de índices seguros adicionais ou auxiliares de wrapper).






