Saltar a contenido

mysql: Biblioteca de controlador MySQL Lua no bloqueante para nginx-module-lua

Instalación

Si no has configurado la suscripción al repositorio RPM, regístrate. Luego puedes proceder con los siguientes pasos.

CentOS/RHEL 7 o 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 con NGINX, asegúrate de que nginx-module-lua esté instalado.

Este documento describe lua-resty-mysql v0.29 lanzado el 14 de enero de 2026.


Esta biblioteca Lua es un controlador cliente MySQL para el módulo ngx_lua de nginx:

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

Esta biblioteca Lua aprovecha la API de cosocket de ngx_lua, que garantiza un comportamiento 100% no bloqueante.

Ten en cuenta que se requiere al menos ngx_lua 0.9.11 o ngx_openresty 1.7.4.1.

Además, también se requiere la biblioteca bit. Si estás utilizando LuaJIT 2 con ngx_lua, entonces la biblioteca bit ya está disponible por defecto.

Sinopsis

    # no necesitas la siguiente línea si estás usando
    # el paquete ngx_openresty:
    server {
        location /test {
            content_by_lua '
                local mysql = require "resty.mysql"
                local db, err = mysql:new()
                if not db then
                    ngx.say("falló al instanciar mysql: ", err)
                    return
                end

                db:set_timeout(1000) -- 1 seg

                -- o conecta a un archivo de socket de dominio unix escuchado
                -- por un 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("falló al conectar: ", err, ": ", errcode, " ", sqlstate)
                    db:close()
                    return
                end

                ngx.say("conectado a mysql.")

                local res, err, errcode, sqlstate =
                    db:query("drop table if exists cats")
                if not res then
                    ngx.say("resultado incorrecto: ", 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 incorrecto: ", err, ": ", errcode, ": ", sqlstate, ".")
                    db:close()
                    return
                end

                ngx.say("tabla cats creada.")

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

                ngx.say(res.affected_rows, " filas insertadas en la tabla cats ",
                        "(último id insertado: ", res.insert_id, ")")

                -- ejecuta una consulta select, se espera alrededor de 10 filas en
                -- el conjunto de resultados:
                res, err, errcode, sqlstate =
                    db:query("select * from cats order by id asc", 10)
                if not res then
                    ngx.say("resultado incorrecto: ", err, ": ", errcode, ": ", sqlstate, ".")
                    db:close()
                    return
                end

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

                -- colócalo en el pool de conexiones de tamaño 100,
                -- con un tiempo de espera máximo inactivo de 10 segundos
                local ok, err = db:set_keepalive(10000, 100)
                if not ok then
                    ngx.say("falló al establecer keepalive: ", err)
                    db:close()
                    return
                end

                -- o simplemente cierra la conexión de inmediato:
                -- local ok, err = db:close()
                -- if not ok then
                --     ngx.say("falló al cerrar: ", err)
                --     return
                -- end
            ';
        }
    }

Métodos

new

syntax: db, err = mysql:new()

Crea un objeto de conexión MySQL. En caso de fallos, devuelve nil y una cadena que describe el error.

connect

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

Intenta conectarse al servidor MySQL remoto.

El argumento options es una tabla Lua que contiene las siguientes claves:

  • host

    el nombre del host para el servidor MySQL. * port

    el puerto en el que el servidor MySQL está escuchando. Por defecto es 3306. * path

    la ruta del archivo de socket unix escuchado por el servidor MySQL. * database

    el nombre de la base de datos MySQL. * user

    nombre de la cuenta MySQL para iniciar sesión. * password

    contraseña de la cuenta MySQL para iniciar sesión (en texto claro). * charset

    el conjunto de caracteres utilizado en la conexión MySQL, que puede ser diferente de la configuración de conjunto de caracteres por defecto. Los siguientes valores son aceptados: 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

    el límite superior para los paquetes de respuesta enviados desde el servidor MySQL (por defecto es 1MB). * ssl

    Si se establece en true, entonces utiliza SSL para conectarse a MySQL (por defecto es false). Si el servidor MySQL no tiene soporte para SSL (o simplemente está deshabilitado), se devolverá la cadena de error "ssl disabled on server". * ssl_verify

    Si se establece en true, entonces verifica la validez del certificado SSL del servidor (por defecto es false). Ten en cuenta que necesitas configurar el lua_ssl_trusted_certificate para especificar el CA (o certificado del servidor) utilizado por tu servidor MySQL. También puede que necesites configurar lua_ssl_verify_depth en consecuencia. * pool

    el nombre para el pool de conexiones MySQL. si se omite, se generará automáticamente un nombre de pool ambiguo con la plantilla de cadena user:database:host:port o user:database:path. (esta opción se introdujo por primera vez en v0.08.)

  • pool_size

    Especifica el tamaño del pool de conexiones. Si se omite y no se proporcionó ninguna opción backlog, no se creará ningún pool. Si se omite pero se proporcionó backlog, el pool se creará con un tamaño por defecto igual al valor de la directiva lua_socket_pool_size. El pool de conexiones mantiene hasta pool_size conexiones vivas listas para ser reutilizadas por llamadas posteriores a connect, pero ten en cuenta que no hay un límite superior para el número total de conexiones abiertas fuera del pool. Si necesitas restringir el número total de conexiones abiertas, especifica la opción backlog. Cuando el pool de conexiones exceda su límite de tamaño, la conexión menos utilizada (mantener viva) que ya está en el pool se cerrará para hacer espacio para la conexión actual. Ten en cuenta que el pool de conexiones de cosocket es por proceso de trabajo de Nginx en lugar de por instancia de servidor Nginx, por lo que el límite de tamaño especificado aquí también se aplica a cada proceso de trabajo de Nginx. También ten en cuenta que el tamaño del pool de conexiones no se puede cambiar una vez que ha sido creado. Ten en cuenta que se requiere al menos ngx_lua 0.10.14 para usar estas opciones.

  • backlog

    Si se especifica, este módulo limitará el número total de conexiones abiertas para este pool. No se pueden abrir más conexiones que pool_size para este pool en ningún momento. Si el pool de conexiones está lleno, las operaciones de conexión posteriores se encolarán en una cola igual al valor de esta opción (la cola de "backlog"). Si el número de operaciones de conexión en cola es igual a backlog, las operaciones de conexión posteriores fallarán y devolverán nil más la cadena de error "too many waiting connect operations". Las operaciones de conexión en cola se reanudarán una vez que el número de conexiones en el pool sea menor que pool_size. La operación de conexión en cola se abortará una vez que hayan estado en cola por más de connect_timeout, controlado por set_timeout, y devolverá nil más la cadena de error "timeout". Ten en cuenta que se requiere al menos ngx_lua 0.10.14 para usar estas opciones.

  • compact_arrays

    cuando esta opción se establece en true, entonces los métodos query y read_result devolverán la estructura de array-de-arrays para el conjunto de resultados, en lugar de la estructura por defecto de array-de-hashes.

Antes de resolver realmente el nombre del host y conectarse al backend remoto, este método siempre buscará en el pool de conexiones conexiones inactivas coincidentes creadas por llamadas anteriores a este método.

set_timeout

syntax: db:set_timeout(time)

Establece el tiempo de espera (en ms) de protección para operaciones posteriores, incluyendo el método connect.

set_keepalive

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

Coloca la conexión MySQL actual inmediatamente en el pool de conexiones de cosocket de ngx_lua.

Puedes especificar el tiempo máximo inactivo (en ms) cuando la conexión está en el pool y el tamaño máximo del pool para cada proceso de trabajo de nginx.

En caso de éxito, devuelve 1. En caso de errores, devuelve nil con una cadena que describe el error.

Solo llama a este método en el lugar donde habrías llamado al método close en su lugar. Llamar a este método convertirá inmediatamente el objeto actual resty.mysql en el estado closed. Cualquier operación posterior diferente de connect() en el objeto actual devolverá el error closed.

get_reused_times

syntax: times, err = db:get_reused_times()

Este método devuelve el número de veces (exitosamente) reutilizadas para la conexión actual. En caso de error, devuelve nil y una cadena que describe el error.

Si la conexión actual no proviene del pool de conexiones incorporado, entonces este método siempre devuelve 0, es decir, la conexión nunca ha sido reutilizada (aún). Si la conexión proviene del pool de conexiones, entonces el valor de retorno siempre es distinto de cero. Por lo tanto, este método también se puede utilizar para determinar si la conexión actual proviene del pool.

close

syntax: ok, err = db:close()

Cierra la conexión mysql actual y devuelve el estado.

En caso de éxito, devuelve 1. En caso de errores, devuelve nil con una cadena que describe el error.

send_query

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

Envía la consulta al servidor MySQL remoto sin esperar sus respuestas.

Devuelve los bytes enviados con éxito en caso de éxito y, de lo contrario, devuelve nil y una cadena que describe el error.

Deberías usar el método read_result para leer las respuestas de MySQL después.

read_result

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

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

Lee un resultado devuelto del servidor MySQL.

Devuelve una tabla Lua (res) que describe el OK packet de MySQL o el result set packet para el resultado de la consulta.

Para consultas que corresponden a un conjunto de resultados, devuelve un array que contiene todas las filas. Cada fila contiene pares clave-valor para cada campo de datos. Por ejemplo,

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

Para consultas que no corresponden a un conjunto de resultados, devuelve una tabla Lua como esta:

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

Si hay más resultados después del resultado actual, un segundo valor de retorno err tendrá la cadena again. Siempre se debe verificar este (segundo) valor de retorno y si es again, entonces se debe llamar a este método nuevamente para recuperar más resultados. Esto suele ocurrir cuando la consulta original contiene múltiples declaraciones (separadas por punto y coma en la misma cadena de consulta) o al llamar a un procedimiento de MySQL. Consulta también Soporte de múltiples conjuntos de resultados.

En caso de errores, este método devuelve como máximo 4 valores: nil, err, errcode y sqlstate. El valor de retorno err contiene una cadena que describe el error, el valor de retorno errcode contiene el código de error de MySQL (un valor numérico) y, finalmente, el valor de retorno sqlstate contiene el código de error SQL estándar que consta de 5 caracteres. Ten en cuenta que, el errcode y sqlstate pueden ser nil si MySQL no los devuelve.

El argumento opcional nrows se puede utilizar para especificar un número aproximado de filas para el conjunto de resultados. Este valor puede ser utilizado para preasignar espacio en la tabla Lua resultante para el conjunto de resultados. Por defecto, toma el valor 4.

query

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

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

Este es un atajo para combinar la llamada a send_query y la primera llamada a read_result.

Siempre debes verificar si el valor de retorno err es again en caso de éxito porque este método solo llamará a read_result una vez por ti. Consulta también Soporte de múltiples conjuntos de resultados.

server_ver

syntax: str = db:server_ver()

Devuelve la cadena de versión del servidor MySQL, como "5.1.64".

Solo debes llamar a este método después de conectarte exitosamente a un servidor MySQL, de lo contrario se devolverá nil.

set_compact_arrays

syntax: db:set_compact_arrays(boolean)

Establece si usar la estructura de "arrays compactos" para los conjuntos de resultados devueltos por consultas posteriores. Consulta la opción compact_arrays para el método connect para más detalles.

Este método se introdujo por primera vez en la versión v0.09.

Cita Literal SQL

Siempre es importante citar correctamente los literales SQL para prevenir ataques de inyección SQL. Puedes usar la función ngx.quote_sql_str proporcionada por ngx_lua para citar valores. Aquí hay un ejemplo:

    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

Soporte de Múltiples Conjuntos de Resultados

Para una consulta SQL que produce múltiples conjuntos de resultados, siempre es tu deber verificar el mensaje de error "again" devuelto por las llamadas a query o read_result, y seguir extrayendo más conjuntos de resultados llamando al método read_result hasta que no se devuelva el mensaje de error "again" (o ocurran otros errores).

A continuación se muestra un ejemplo trivial para esto:

    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, "falló al 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 incorrecto #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 incorrecto #", 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, "falló al establecer keepalive: ", err)
        db:close()
        ngx.exit(500)
    end

Este fragmento de código producirá los siguientes datos en el cuerpo de la respuesta:

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

Depuración

Usualmente es conveniente usar la biblioteca lua-cjson para codificar los valores de retorno de los métodos de consulta MySQL a JSON. Por ejemplo,

    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 Errores

Por defecto, el módulo subyacente ngx_lua realiza el registro de errores cuando ocurren errores de socket. Si ya estás realizando un manejo adecuado de errores en tu propio código Lua, se recomienda desactivar este registro automático de errores desactivando la directiva lua_socket_log_errors de ngx_lua, es decir,

    lua_socket_log_errors off;

Limitaciones

  • Esta biblioteca no se puede usar en contextos de código como init_by_lua, set_by_lua, log_by_lua, y header_filter_by_lua donde la API de cosocket de ngx_lua no está disponible.
  • La instancia del objeto resty.mysql no puede ser almacenada en una variable Lua a nivel de módulo Lua, porque entonces será compartida por todas las solicitudes concurrentes manejadas por el mismo proceso de trabajo de nginx (ver https://github.com/openresty/lua-nginx-module#data-sharing-within-an-nginx-worker) y resultará en malas condiciones de carrera cuando las solicitudes concurrentes intenten usar la misma instancia de resty.mysql. Siempre debes iniciar los objetos resty.mysql en variables locales de función o en la tabla ngx.ctx. Estos lugares tienen sus propias copias de datos para cada solicitud.

Más Soporte para Métodos de Autenticación

Por defecto, de todos los métodos de autenticación, solo se soportan Autenticación de Contraseña Antigua (mysql_old_password) y Autenticación de Contraseña Segura (mysql_native_password). Si el servidor requiere sha256_password o cache_sha2_password, puede devolverse un error como auth plugin caching_sha2_password or sha256_password are not supported because resty.rsa is not installed.

Necesitas lua-resty-rsa al usar sha256_password y cache_sha2_password.

Ver También

GitHub

Puedes encontrar consejos de configuración adicionales y documentación para este módulo en el repositorio de GitHub para nginx-module-mysql.