Pular para conteúdo

mysql: Biblioteca de driver MySQL Lua não bloqueante para nginx-module-lua

Instalação

Se você ainda não configurou a assinatura do repositório RPM, inscreva-se. Então você pode prosseguir com os seguintes passos.

CentOS/RHEL 7 ou Amazon Linux 2

yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install lua-resty-mysql

CentOS/RHEL 8+, Fedora Linux, Amazon Linux 2023

dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-mysql

Para usar esta biblioteca Lua com NGINX, certifique-se de que o nginx-module-lua está instalado.

Este documento descreve lua-resty-mysql v0.29 lançado em 14 de janeiro de 2026.


Esta biblioteca Lua é um driver cliente MySQL para o módulo ngx_lua do nginx:

https://github.com/openresty/lua-nginx-module

Esta biblioteca Lua aproveita a API de cosocket do ngx_lua, que garante um comportamento 100% não bloqueante.

Observe que é necessário pelo menos ngx_lua 0.9.11 ou ngx_openresty 1.7.4.1.

Além disso, a biblioteca bit também é necessária. Se você estiver usando LuaJIT 2 com ngx_lua, então a biblioteca bit já está disponível por padrão.

Sinopse

    # você não precisa da linha a seguir se estiver usando
    # o pacote ngx_openresty:
    server {
        location /test {
            content_by_lua '
                local mysql = require "resty.mysql"
                local db, err = mysql:new()
                if not db then
                    ngx.say("falha ao instanciar mysql: ", err)
                    return
                end

                db:set_timeout(1000) -- 1 seg

                -- ou conecte-se a um arquivo de socket de domínio unix escutado
                -- por um servidor mysql:
                --     local ok, err, errcode, sqlstate =
                --           db:connect{
                --              path = "/path/to/mysql.sock",
                --              database = "ngx_test",
                --              user = "ngx_test",
                --              password = "ngx_test" }

                local ok, err, errcode, sqlstate = db:connect{
                    host = "127.0.0.1",
                    port = 3306,
                    database = "ngx_test",
                    user = "ngx_test",
                    password = "ngx_test",
                    charset = "utf8",
                    max_packet_size = 1024 * 1024,
                }

                if not ok then
                    ngx.say("falha ao conectar: ", err, ": ", errcode, " ", sqlstate)
                    db:close()
                    return
                end

                ngx.say("conectado ao mysql.")

                local res, err, errcode, sqlstate =
                    db:query("drop table if exists cats")
                if not res then
                    ngx.say("resultado ruim: ", err, ": ", errcode, ": ", sqlstate, ".")
                    db:close()
                    return
                end

                res, err, errcode, sqlstate =
                    db:query("create table cats "
                             .. "(id serial primary key, "
                             .. "name varchar(5))")
                if not res then
                    ngx.say("resultado ruim: ", err, ": ", errcode, ": ", sqlstate, ".")
                    db:close()
                    return
                end

                ngx.say("tabela cats criada.")

                res, err, errcode, sqlstate =
                    db:query("insert into cats (name) "
                             .. "values (\'Bob\'),(\'\'),(null)")
                if not res then
                    ngx.say("resultado ruim: ", err, ": ", errcode, ": ", sqlstate, ".")
                    db:close()
                    return
                end

                ngx.say(res.affected_rows, " linhas inseridas na tabela cats ",
                        "(último id inserido: ", res.insert_id, ")")

                -- execute uma consulta select, espera cerca de 10 linhas no
                -- conjunto de resultados:
                res, err, errcode, sqlstate =
                    db:query("select * from cats order by id asc", 10)
                if not res then
                    ngx.say("resultado ruim: ", err, ": ", errcode, ": ", sqlstate, ".")
                    db:close()
                    return
                end

                local cjson = require "cjson"
                ngx.say("resultado: ", cjson.encode(res))

                -- coloque-o no pool de conexões de tamanho 100,
                -- com 10 segundos de tempo máximo ocioso
                local ok, err = db:set_keepalive(10000, 100)
                if not ok then
                    ngx.say("falha ao definir keepalive: ", err)
                    db:close()
                    return
                end

                -- ou simplesmente feche a conexão imediatamente:
                -- local ok, err = db:close()
                -- if not ok then
                --     ngx.say("falha ao fechar: ", err)
                --     return
                -- end
            ';
        }
    }

Métodos

new

syntax: db, err = mysql:new()

Cria um objeto de conexão MySQL. Em caso de falhas, retorna nil e uma string descrevendo o erro.

connect

syntax: ok, err, errcode, sqlstate = db:connect(options)

Tenta conectar ao servidor MySQL remoto.

O argumento options é uma tabela Lua contendo as seguintes chaves:

  • host

    o nome do host para o servidor MySQL. * port

    a porta que o servidor MySQL está escutando. Padrão é 3306. * path

    o caminho do arquivo de socket unix escutado pelo servidor MySQL. * database

    o nome do banco de dados MySQL. * user

    nome da conta MySQL para login. * password

    senha da conta MySQL para login (em texto claro). * charset

    o conjunto de caracteres usado na conexão MySQL, que pode ser diferente da configuração de charset padrão. Os seguintes valores são aceitos: big5, dec8, cp850, hp8, koi8r, latin1, latin2, swe7, ascii, ujis, sjis, hebrew, tis620, euckr, koi8u, gb2312, greek, cp1250, gbk, latin5, armscii8, utf8, ucs2, cp866, keybcs2, macce, macroman, cp852, latin7, utf8mb4, cp1251, utf16, utf16le, cp1256, cp1257, utf32, binary, geostd8, cp932, eucjpms, gb18030. * max_packet_size

    o limite superior para os pacotes de resposta enviados pelo servidor MySQL (padrão é 1MB). * ssl

    Se definido como true, então usa SSL para conectar ao MySQL (padrão é false). Se o servidor MySQL não tiver suporte a SSL (ou apenas estiver desativado), a string de erro "ssl disabled on server" será retornada. * ssl_verify

    Se definido como true, então verifica a validade do certificado SSL do servidor (padrão é false). Observe que você precisa configurar o lua_ssl_trusted_certificate para especificar o CA (ou servidor) certificado usado pelo seu servidor MySQL. Você também pode precisar configurar o lua_ssl_verify_depth de acordo. * pool

    o nome para o pool de conexões MySQL. se omitido, um nome de pool ambíguo será gerado automaticamente com o template de string user:database:host:port ou user:database:path. (esta opção foi introduzida pela primeira vez na v0.08.)

  • pool_size

    Especifica o tamanho do pool de conexões. Se omitido e nenhuma opção backlog foi fornecida, nenhum pool será criado. Se omitido, mas backlog foi fornecido, o pool será criado com um tamanho padrão igual ao valor da diretiva lua_socket_pool_size. O pool de conexões mantém até pool_size conexões ativas prontas para serem reutilizadas por chamadas subsequentes a connect, mas observe que não há limite superior para o número total de conexões abertas fora do pool. Se você precisar restringir o número total de conexões abertas, especifique a opção backlog. Quando o pool de conexões exceder seu limite de tamanho, a conexão menos recentemente usada (mantida ativa) já no pool será fechada para abrir espaço para a conexão atual. Observe que o pool de conexões de cosocket é por processo de trabalhador do Nginx, em vez de por instância do servidor Nginx, portanto, o limite de tamanho especificado aqui também se aplica a cada processo de trabalhador do Nginx. Também observe que o tamanho do pool de conexões não pode ser alterado uma vez que foi criado. Observe que é necessário pelo menos ngx_lua 0.10.14 para usar essas opções.

  • backlog

    Se especificado, este módulo limitará o número total de conexões abertas para este pool. Não mais conexões do que pool_size podem ser abertas para este pool a qualquer momento. Se o pool de conexões estiver cheio, operações de conexão subsequentes serão enfileiradas em uma fila igual ao valor desta opção (a fila "backlog"). Se o número de operações de conexão enfileiradas for igual a backlog, operações de conexão subsequentes falharão e retornarão nil mais a string de erro "too many waiting connect operations". As operações de conexão enfileiradas serão retomadas uma vez que o número de conexões no pool for menor que pool_size. A operação de conexão enfileirada será abortada uma vez que tenha sido enfileirada por mais de connect_timeout, controlada por set_timeout, e retornará nil mais a string de erro "timeout". Observe que é necessário pelo menos ngx_lua 0.10.14 para usar essas opções.

  • compact_arrays

    quando esta opção é definida como verdadeira, então os métodos query e read_result retornarão a estrutura de array-de-arrays para o conjunto de resultados, em vez da estrutura padrão de array-de-hashes.

Antes de realmente resolver o nome do host e conectar ao backend remoto, este método sempre procurará no pool de conexões por conexões ociosas correspondentes criadas por chamadas anteriores deste método.

set_timeout

syntax: db:set_timeout(time)

Define o tempo limite (em ms) de proteção para operações subsequentes, incluindo o método connect.

set_keepalive

syntax: ok, err = db:set_keepalive(max_idle_timeout, pool_size)

Coloca a conexão MySQL atual imediatamente no pool de conexões cosocket do ngx_lua.

Você pode especificar o tempo máximo ocioso (em ms) quando a conexão está no pool e o tamanho máximo do pool para cada processo de trabalhador do nginx.

Em caso de sucesso, retorna 1. Em caso de erros, retorna nil com uma string descrevendo o erro.

Chame este método apenas no lugar onde você teria chamado o método close. Chamar este método imediatamente transformará o objeto resty.mysql atual no estado closed. Qualquer operação subsequente além de connect() no objeto atual retornará o erro closed.

get_reused_times

syntax: times, err = db:get_reused_times()

Este método retorna o número de vezes (com sucesso) reutilizadas para a conexão atual. Em caso de erro, retorna nil e uma string descrevendo o erro.

Se a conexão atual não vier do pool de conexões embutido, então este método sempre retornará 0, ou seja, a conexão nunca foi reutilizada (ainda). Se a conexão vem do pool de conexões, então o valor retornado é sempre diferente de zero. Portanto, este método também pode ser usado para determinar se a conexão atual vem do pool.

close

syntax: ok, err = db:close()

Fecha a conexão mysql atual e retorna o status.

Em caso de sucesso, retorna 1. Em caso de erros, retorna nil com uma string descrevendo o erro.

send_query

syntax: bytes, err = db:send_query(query)

Envia a consulta para o servidor MySQL remoto sem esperar suas respostas.

Retorna os bytes enviados com sucesso em caso de sucesso e, caso contrário, retorna nil e uma string descrevendo o erro.

Você deve usar o método read_result para ler as respostas do MySQL posteriormente.

read_result

syntax: res, err, errcode, sqlstate = db:read_result()

syntax: res, err, errcode, sqlstate = db:read_result(nrows)

Lê um resultado retornado do servidor MySQL.

Retorna uma tabela Lua (res) descrevendo o OK packet ou result set packet do MySQL para o resultado da consulta.

Para consultas que correspondem a um conjunto de resultados, retorna um array contendo todas as linhas. Cada linha contém pares chave-valor para cada campo de dados. Por exemplo,

    {
        { name = "Bob", age = 32, phone = ngx.null },
        { name = "Marry", age = 18, phone = "10666372"}
    }

Para consultas que não correspondem a um conjunto de resultados, retorna uma tabela Lua assim:

    {
        insert_id = 0,
        server_status = 2,
        warning_count = 1,
        affected_rows = 32,
        message = nil
    }

Se mais resultados seguirem o resultado atual, um segundo valor de retorno err será dado com a string again. Deve-se sempre verificar este (segundo) valor de retorno e, se for again, então deve-se chamar este método novamente para recuperar mais resultados. Isso geralmente acontece quando a consulta original contém várias instruções (separadas por ponto e vírgula na mesma string de consulta) ou ao chamar um procedimento MySQL. Veja também Suporte a Múltiplos Conjuntos de Resultados.

Em caso de erros, este método retorna no máximo 4 valores: nil, err, errcode e sqlstate. O valor de retorno err contém uma string descrevendo o erro, o valor de retorno errcode contém o código de erro do MySQL (um valor numérico) e, finalmente, o valor de retorno sqlstate contém o código de erro SQL padrão que consiste em 5 caracteres. Observe que os valores errcode e sqlstate podem ser nil se o MySQL não os retornar.

O argumento opcional nrows pode ser usado para especificar um número aproximado de linhas para o conjunto de resultados. Este valor pode ser usado para pré-alocar espaço na tabela Lua resultante para o conjunto de resultados. Por padrão, ele assume o valor 4.

query

syntax: res, err, errcode, sqlstate = db:query(query)

syntax: res, err, errcode, sqlstate = db:query(query, nrows)

Este é um atalho para combinar a chamada send_query e a primeira chamada read_result.

Você deve sempre verificar se o valor de retorno err é again em caso de sucesso, porque este método só chamará read_result uma vez para você. Veja também Suporte a Múltiplos Conjuntos de Resultados.

server_ver

syntax: str = db:server_ver()

Retorna a string da versão do servidor MySQL, como "5.1.64".

Você deve chamar este método apenas após conectar-se com sucesso a um servidor MySQL, caso contrário, nil será retornado.

set_compact_arrays

syntax: db:set_compact_arrays(boolean)

Define se deve usar a estrutura "compact-arrays" para os conjuntos de resultados retornados por consultas subsequentes. Veja a opção compact_arrays para o método connect para mais detalhes.

Este método foi introduzido pela primeira vez na versão v0.09.

Citação Literal SQL

É sempre importante citar literais SQL corretamente para prevenir ataques de injeção SQL. Você pode usar a função ngx.quote_sql_str fornecida pelo ngx_lua para citar valores. Aqui está um exemplo:

    local name = ngx.unescape_uri(ngx.var.arg_name)
    local quoted_name = ngx.quote_sql_str(name)
    local sql = "select * from users where name = " .. quoted_name

Suporte a Múltiplos Conjuntos de Resultados

Para uma consulta SQL que produz múltiplos conjuntos de resultados, é sempre seu dever verificar a mensagem de erro "again" retornada pelas chamadas dos métodos query ou read_result, e continuar puxando mais conjuntos de resultados chamando o método read_result até que nenhuma mensagem de erro "again" seja retornada (ou que ocorram outros erros).

Abaixo está um exemplo trivial para isso:

    local cjson = require "cjson"
    local mysql = require "resty.mysql"

    local db = mysql:new()
    local ok, err, errcode, sqlstate = db:connect({
        host = "127.0.0.1",
        port = 3306,
        database = "world",
        user = "monty",
        password = "pass"})

    if not ok then
        ngx.log(ngx.ERR, "falha ao conectar: ", err, ": ", errcode, " ", sqlstate)
        return ngx.exit(500)
    end

    res, err, errcode, sqlstate = db:query("select 1; select 2; select 3;")
    if not res then
        ngx.log(ngx.ERR, "resultado ruim #1: ", err, ": ", errcode, ": ", sqlstate, ".")
        db:close()
        return ngx.exit(500)
    end

    ngx.say("resultado #1: ", cjson.encode(res))

    local i = 2
    while err == "again" do
        res, err, errcode, sqlstate = db:read_result()
        if not res then
            ngx.log(ngx.ERR, "resultado ruim #", i, ": ", err, ": ", errcode, ": ", sqlstate, ".")
            db:close()
            return ngx.exit(500)
        end

        ngx.say("resultado #", i, ": ", cjson.encode(res))
        i = i + 1
    end

    local ok, err = db:set_keepalive(10000, 50)
    if not ok then
        ngx.log(ngx.ERR, "falha ao definir keepalive: ", err)
        db:close()
        ngx.exit(500)
    end

Este trecho de código produzirá os seguintes dados de corpo de resposta:

resultado #1: [{"1":"1"}]
resultado #2: [{"2":"2"}]
resultado #3: [{"3":"3"}]

Depuração

É geralmente conveniente usar a biblioteca lua-cjson para codificar os valores de retorno dos métodos de consulta MySQL em JSON. Por exemplo,

    local cjson = require "cjson"
    ...
    local res, err, errcode, sqlstate = db:query("select * from cats")
    if res then
        print("res: ", cjson.encode(res))
    end

Registro Automático de Erros

Por padrão, o módulo subjacente ngx_lua faz o registro de erros quando ocorrem erros de socket. Se você já estiver fazendo o tratamento de erros adequados em seu próprio código Lua, então é recomendado desativar este registro automático de erros desligando a diretiva lua_socket_log_errors do ngx_lua, ou seja,

    lua_socket_log_errors off;

Limitações

  • Esta biblioteca não pode ser usada em contextos de código como init_by_lua, set_by_lua, log_by_lua, e header_filter_by_lua onde a API de cosocket do ngx_lua não está disponível.
  • A instância do objeto resty.mysql não pode ser armazenada em uma variável Lua no nível do módulo Lua, porque então será compartilhada por todas as requisições concorrentes tratadas pelo mesmo processo de trabalhador do nginx (veja https://github.com/openresty/lua-nginx-module#data-sharing-within-an-nginx-worker) e resultará em más condições de corrida quando requisições concorrentes tentarem usar a mesma instância de resty.mysql. Você deve sempre iniciar objetos resty.mysql em variáveis locais de função ou na tabela ngx.ctx. Esses locais têm suas próprias cópias de dados para cada requisição.

Mais Suporte a Métodos de Autenticação

Por padrão, de todos os métodos de autenticação, apenas Old Password Authentication(mysql_old_password) e Secure Password Authentication(mysql_native_password) são suportados. Se o servidor exigir sha256_password ou cache_sha2_password, um erro como auth plugin caching_sha2_password or sha256_password are not supported because resty.rsa is not installed pode ser retornado.

Necessita de lua-resty-rsa ao usar sha256_password e cache_sha2_password.

Veja Também

GitHub

Você pode encontrar dicas adicionais de configuração e documentação para este módulo no repositório GitHub para nginx-module-mysql.