Comparação técnica detalhada entre o mysql_samp (Rust, v0.2.0) e o MySQL R41-4 (C++, pBlueG/maddinat0r).
Os arquivos prontos estão em benchmark/:
| Arquivo | Descrição |
|---|---|
benchmark/setup.sql |
Cria as tabelas e insere 100 registros de teste |
benchmark/bench_mysql_samp.pwn |
Gamemode de benchmark para o mysql_samp |
benchmark/bench_r41.pwn |
Gamemode de benchmark para o R41-4 |
1. Configure o banco de dados:
mysql -u root -p nome_do_banco < benchmark/setup.sql
2. Edite as credenciais em bench_mysql_samp.pwn e bench_r41.pwn:
#define DB_HOST "127.0.0.1"
#define DB_USER "root"
#define DB_PASS "senha"
#define DB_NAME "benchmark"
3. Compile e execute cada GM separadamente no seu servidor, com o plugin correspondente carregado. O resultado aparece no console.
4. Compare os números das 4 etapas entre os dois plugins.
| Etapa | Tipo | Rounds |
|---|---|---|
| 1 | SELECT sequencial (FIFO) | 500 queries |
| 2 | SELECT paralelo | 500 queries |
| 3 | INSERT paralelo | 200 queries |
| 4 | mysql_format com escape (puro CPU) |
50.000 iterações |
Ambiente: SA-MP 0.3.7-R2, MySQL local (loopback — 127.0.0.1), Linux x86. Ambos os plugins testados na mesma máquina que hospeda o MySQL, sem latência de rede.
Nota sobre conexão remota: com MySQL em servidor separado (RTT ≥ 1ms), cada query levaria mais de um tick para completar. Isso eliminaria a vantagem do R41-4 nos SELECTs, pois seu despacho via
process_tickfica limitado a uma query por tick. Os resultados abaixo representam o melhor cenário possível para o R41-4.
| Etapa | Total | Média | Throughput |
|---|---|---|---|
SELECT FIFO — mysql_tquery (500x)¹ |
93 ms | 0,186 ms/q | 5.376 q/s |
SELECT paralelo — mysql_pquery (500x) |
51 ms | 0,101 ms/q | 9.804 q/s |
INSERT paralelo — mysql_pquery (200x) |
607 ms | 3,035 ms/q | 329 q/s |
mysql_format com escape (50.000x) |
64 ms | 0,0012 ms | 781.250/s |
| Etapa | Total | Média | Throughput |
|---|---|---|---|
SELECT FIFO — mysql_query (500x)¹ |
155 ms | 0,310 ms/q | 3.226 q/s |
SELECT paralelo — mysql_pquery (500x) |
94 ms | 0,187 ms/q | 5.319 q/s |
INSERT paralelo — mysql_pquery (200x) |
45 ms | 0,224 ms/q | 4.444 q/s |
mysql_format com escape (50.000x) |
135 ms | 0,0027 ms | 370.370/s |
| Etapa | R41-4 | mysql_samp | Vencedor |
|---|---|---|---|
| SELECT FIFO (500x)¹ | 0,186 ms/q — 5.376 q/s | 0,310 ms/q — 3.226 q/s | R41-4 1,7x |
| SELECT paralelo (500x) | 0,101 ms/q — 9.804 q/s | 0,187 ms/q — 5.319 q/s | R41-4 1,85x |
| INSERT paralelo (200x) | 3,035 ms/q — 329 q/s | 0,224 ms/q — 4.444 q/s | mysql_samp 13,5x |
| mysql_format (50.000x) | 0,0012 ms — 781k/s | 0,0027 ms — 370k/s | R41-4 2,1x |
¹ A Etapa 1 não é uma comparação direta — ver nota abaixo.
O R41-4 não tem equivalente direto ao mysql_query do mysql_samp. O mais próximo é mysql_tquery, que foi o usado. A diferença é arquitetural:
| Plugin | Native FIFO | Comportamento |
|---|---|---|
| R41-4 | mysql_tquery |
Pool de threads; callbacks despachados via process_tick — vários por tick quando o MySQL responde rápido (loopback) |
| mysql_samp | mysql_query |
Uma thread por query; resultados reordenados via canal mpsc, callbacks entregues em ordem sem depender do tick |
Em semântica do gamemode os dois são equivalentes (N queries, callbacks em ordem de submissão). O throughput do R41-4 na Etapa 1 depende da velocidade do MySQL: em loopback vence; em MySQL remoto perde, pois o despacho via tick passa a ser o gargalo.
| Critério | mysql_samp | R41-4 |
|---|---|---|
| SELECT FIFO (500x)¹ | 0,310 ms/q — 3.226 q/s | 0,186 ms/q — 5.376 q/s |
| SELECT paralelo (500x) | 0,187 ms/q — 5.319 q/s | 0,101 ms/q — 9.804 q/s |
| INSERT paralelo (200x) | 0,224 ms/q — 4.444 q/s | 3,035 ms/q — 329 q/s |
| mysql_format (50.000x) | 0,0027 ms — 370k/s | 0,0012 ms — 781k/s |
| Segurança de memória | Zero falhas garantidas pelo compilador | Segfaults documentados (issues #291, #310+) |
SQL injection via %s |
Impossível (%s escapa por padrão) |
Possível (%s é raw no R41-4) |
| Vazamento de memória | Impossível (cache gerenciado automaticamente) | Possível sem cache_delete() |
| Dependências runtime | Nenhuma | MySQL C Connector + Boost |
| Issues em aberto | Novo (em desenvolvimento ativo) | 50+ issues abertos no GitHub |
| Query síncrona bloqueante | Removida (nunca bloqueia o server tick) | Existe (mysql_query bloqueia) |
O R41-4 usa C++ com ponteiros brutos e gestão manual de memória. Bugs documentados publicamente:
O próprio repositório do R41-4 avisa:
“Use
cache_delete()if you don’t need the query’s result anymore or you will experience memory leaks.”
O Rust garante em tempo de compilação:
Send e Sync garantem que apenas um thread acessa dados mutuamente exclusivoscache_delete() manual; o Rust libera automaticamente// Impossível em Rust — o compilador rejeita em tempo de build:
let cache = get_cache();
drop(cache);
use_cache(cache); // ERRO: value moved here — não chega a virar binário
Resultado prático: o mysql_samp nunca vai derrubar seu servidor por falha de memória. O R41-4 pode.
%sNo R41-4, o especificador %s insere a string sem escape. Código de um gamemode real:
// R41-4 — VULNERÁVEL a SQL injection
new query[256];
mysql_format(gMysql, query, sizeof(query),
"SELECT * FROM accounts WHERE name = '%s'", inputName);
// Se inputName = "' OR 1=1 -- "
// Query gerada: SELECT * FROM accounts WHERE name = '' OR 1=1 -- '
// Resultado: retorna TODAS as contas
Para escapar no R41-4 era necessário usar %e explicitamente — algo que a maioria dos gamemodes nunca fez.
// mysql_samp — SEGURO por padrão
new query[256];
mysql_format(gMysql, query, sizeof(query),
"SELECT * FROM accounts WHERE name = '%s'", inputName);
// Se inputName = "' OR 1=1 -- "
// Query gerada: SELECT * FROM accounts WHERE name = '\' OR 1=1 -- '
// Resultado: nenhuma linha (string escapada corretamente)
%s sempre escapa. Para inserir valores raw confiáveis (nomes de tabela, SQL dinâmico), use %r explicitamente.
Resultado prático: gamemodes migrados do R41-4 ficam automaticamente protegidos. Gamemodes novos não precisam pensar em escaping.
Para funcionar, o servidor precisa ter instalado:
| Biblioteca | Versão | Observação |
|---|---|---|
libmysqlclient |
5.5+ / 6.1 | Sistema ou bundled |
Boost |
1.57+ | Compilado no plugin |
libz.so.1 |
Sistema | Reportado em issue #292 |
Erros típicos de configuração:
error while loading shared libraries: libmysqlclient.so.18: cannot open shared object file
libz.so.1: cannot open shared object file: No such file or directory
Zero dependências externas. O binário é completamente autocontido:
mysql com feature default-rust (sem libmysqlclient)Basta copiar o .so ou .dll para a pasta plugins/. Funciona imediatamente em qualquer distribuição Linux.
GameMode ──► mysql_query() ──► SÍNCRONO — bloqueia o server tick até query retornar
──► mysql_tquery() ──► 1 worker thread por conexão (FIFO)
──► mysql_pquery() ──► pool de conexões paralelas (sem ordem)
Fila: Boost lockfree::spsc_queue (capacidade < 65.536 entradas)
Sincronização: std::mutex em cada chamada MySQL C API
Problema: mysql_query() síncrono bloqueia o server tick. Se a query demorar 100 ms, o servidor fica frozen por 100 ms — nenhum jogador recebe pacotes nesse intervalo.
GameMode ──► mysql_query() ──► fila FIFO via mpsc channel (NUNCA bloqueia)
──► mysql_pquery() ──► pool paralelo via mpsc channel
Sincronização: garantida pelo sistema de tipos do Rust (sem mutexes manuais)
Não existe query síncrona. O mysql_samp eliminou mysql_query() bloqueante por design — impossível travar o servidor por acidente.
Baseado nos issues públicos de https://github.com/pBlueG/SA-MP-MySQL:
| Issue | Descrição | Status |
|---|---|---|
| #291 | “FREE RESULT MISSING” — resultado não liberado corretamente | Aberto |
| #288 | SSL não funciona no Linux | Aberto |
| #277 | mysql_tquery não executa UPDATE em certas condições |
Aberto |
| #292 | libz.so.1: cannot open shared object file |
Aberto |
| Múltiplos | Segmentation fault (SIGSEGV) no shutdown com queries pendentes | Abertos |
| Múltiplos | Incompatibilidade com sampgdk e outros plugins | Abertos |
O mysql_samp não apresenta nenhum desses problemas por design:
// R41-4 — by-ref (verboso)
new rows;
cache_get_row_count(rows);
new score;
cache_get_value_name_int(0, "score", score);
// mysql_samp — retorno direto (limpo)
new rows = cache_get_row_count();
new score = cache_get_value_name_int(0, "score");
// R41-4 — DEVE chamar cache_delete ou há vazamento de memória
public OnPlayerData(playerid)
{
// ... lê dados ...
cache_delete(cache_save()); // obrigatório
}
// mysql_samp — sem cache_delete necessário
// O cache ativo é liberado automaticamente após o callback
public OnPlayerData(playerid)
{
// ... lê dados ...
// nada a fazer
}
// R41-4 — tags customizadas (podem causar warnings)
new MySQL:gMysql = mysql_connect(...);
new Cache:cache = cache_save();
new ORM:orm = orm_create("table", gMysql);
// mysql_samp — sem tags
new gMysql = mysql_connect(...);
new cache = cache_save();
new orm = orm_create("table", gMysql);
| Feature | mysql_samp | R41-4 |
|---|---|---|
| Queries FIFO (threaded) | mysql_query |
mysql_tquery |
| Queries paralelas | mysql_pquery |
mysql_pquery |
| Queries síncronas bloqueantes | Removida por segurança | mysql_query (legado) |
| ORM | Sim (orm_*) |
Sim (orm_*) |
| Cache salvo | cache_save() / cache_set_active() |
Igual |
mysql_format %s |
Escapa automaticamente | Raw (inseguro) |
mysql_format %e |
Alias de %s (escape) |
Igual ao mysql_samp |
mysql_format %r |
Raw (sem escape) | Não existe |
| Tags Pawn | Sem tags | MySQL:, Cache:, ORM: |
| Dependências runtime | Nenhuma | libssl + libmysqlclient + Boost |
| open.mp | Compatível | Compatível |
| SSL/TLS | rustls (embutido) | Via MySQL C Connector |
| Multi-result sets | Não suportado | cache_set_result() |
| Segfault possível | Não | Sim (documentado) |
| Vazamento de cache | Impossível | Possível sem cache_delete() |
| Issues conhecidos críticos | Nenhum em produção | 50+ abertos no GitHub |
Os resultados medidos em loopback (melhor cenário para o R41-4) mostram um quadro honesto:
O R41-4 tem throughput de SELECT mais alto em loopback — 1,7–1,85x em SELECT FIFO e paralelo, e 2,1x em mysql_format. Isso acontece porque, com MySQL respondendo em < 0,1ms (abaixo de um tick), o R41-4 consegue despachar múltiplos callbacks por tick e aproveita totalmente o cache do banco.
O mysql_samp tem 13,5x mais throughput em INSERT — escritas invalidam cache e forçam I/O real, tornando o gargalo de callback-via-tick do R41-4 irrelevante e expondo a diferença arquitetural. Gamemodes reais fazem muito mais escrita (save de jogador, logs, eventos) do que leitura repetida da mesma row.
O loopback é o melhor cenário possível para o R41-4. Com MySQL remoto (RTT ≥ 1ms, o que inclui a maioria dos ambientes de produção), cada query passa a durar mais que um tick e a vantagem de SELECT do R41-4 desaparece — o mysql_samp passa a vencer em todas as etapas.
As vantagens do mysql_samp são estruturais, independentes de benchmark:
%s escapa sem que o desenvolvedor precise lembrarmysql_query que congela o server tick; o mysql_samp nãoPara novos projetos, o mysql_samp é a escolha tecnicamente superior. Para projetos existentes que precisam de compatibilidade com código legado do R41-4, consulte migracao.md.