Economize 20% com WELCOMEVer ofertas
mysql assíncrono oxmysql

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:


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 :nome parâmetros (nomeados); substituir MySQL.Async.* chamadas com MySQL.*/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

  1. Backup completo: mysqldump --single-transaction yourdb > backup.sql.
  2. Ambiente de preparação espelhamento de esquema de produção + subconjunto de dados.
  3. Artefato e dependências: Build atual do FXServer, mais recente oxmysql.
  4. Janela de tempo de inatividade para troca de produto (geralmente < 5 minutos).
  5. 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
# garantir mysql-async # ← comente ou remova

# Iniciar oxmysql
default_prio 50
ensure oxmysql

# String de conexão consumida pelo oxmysql
set mysql_connection_string "mysql://user:pass@127.0.0.1:3306/yourdb?charset=utf8mb4"

# Diagnósticos opcionais
set mysql_slow_query_warning 200 # registrar consultas mais lentas que 200ms
set mysql_debug false # true para registro detalhado durante a fase de preparação

Manter mysql-assíncrono desabilitado, 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: @param tabelas 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 :nome via objeto

3.1 Substituições comuns

SELECIONE muitos

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

SELECIONE uma única linha

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

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

SELECIONE escalar (por exemplo, contagem, id)

-- mysql-async

-- oxmysql
local count = MySQL.scalar.await(
 'SELECT COUNT(*) FROM owned_vehicles WHERE owner = ?',
 { owner }
)

INSERIR (obter insertId)

-- mysql-async (execute)

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

ATUALIZAR/EXCLUIR (linhas afetadas)

-- mysql-async (execute)

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

Transações (manual)

-- oxmysql transação manual
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 adicionam MySQL.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): @nome marcadores de posição com uma tabela: { ['@nome']=valor }
  • oxmysql (posicional): ? marcadores de posição com um variedade: { valor1, valor2 }
  • oxmysql (nomeado): :nome marcadores 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 }
)

-- Lista IN (...)
-- Construir placeholders dinamicamente e passar um array plano
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)

-- Campos JSON (MySQL 5.7+/MariaDB 10.2+)
local name = MySQL.scalar.await('SELECT JSON_UNQUOTE(JSON_EXTRACT(data, "$.name")) FROM players WHERE 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 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+

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

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

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

  1. Reverta suas alterações de recursos (mantenha um legado-mysql-async filial).
  2. Em servidor.cfg trocar: # garante oxmysql garante mysql-async
  3. 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)

  1. Congelar implantações, faça backup do banco de dados.
  2. Aplicar Seção 5 “UP” SQL em preparação → verificar → produção.
  3. Refatoração de código de confirmação: substituição de chamadas (Seção 3) + estilos de parâmetros (Seção 4).
  4. Implantar, garantir oxmysql, reinicie o servidor.
  5. Correr testes de fumaça (login, contracheques, inventário, geração/desaparecimento de veículos, banimentos, dinheiro da sociedade, alternância de funções).
  6. Observe os registros por 15 a 30 minutos (aviso_de_consulta_lenta_do_mysql ajuda); 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

  1. Coloque o recurso em uma pasta (por exemplo, banco de bois/), adicionar garantir banco de bois para servidor.cfg.
  2. Console do servidor Tail; os resultados são impressos como linhas como: [banco] SELECIONE único: 134,21 ms.
  3. Para um antes/depois comparação, execute uma vez com mysql-assíncrono (ajuste as chamadas se necessário), então com oxmysql.

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/.escalar com LIMITE 1 quando 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/JUNTAR coluna.
  • Evitar SELECIONE * em caminhos quentes.
  • Registre consultas lentas e acompanhe os principais infratores semanalmente.

Links internos


Créditos
Mantido por fivemx.com. Contribuições são bem-vindas (envie diffs de índices seguros adicionais ou auxiliares de wrapper).