跳转至

mysql: 非阻塞 Lua MySQL 驱动库用于 nginx-module-lua

安装

如果您尚未设置 RPM 仓库订阅,请 注册。然后,您可以继续以下步骤。

CentOS/RHEL 7 或 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

要在 NGINX 中使用此 Lua 库,请确保已安装 nginx-module-lua

本文档描述了 lua-resty-mysql v0.29,于 2026 年 1 月 14 日发布。


此 Lua 库是 ngx_lua NGINX 模块的 MySQL 客户端驱动:

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

此 Lua 库利用了 ngx_lua 的 cosocket API,确保 100% 非阻塞行为。

请注意,至少需要 ngx_lua 0.9.11ngx_openresty 1.7.4.1

此外,还需要 bit library。如果您使用的是与 ngx_lua 兼容的 LuaJIT 2,则 bit 库默认已经可用。

概述

    # 如果您使用的是 ngx_openresty 包,则不需要以下行:
    server {
        location /test {
            content_by_lua '
                local mysql = require "resty.mysql"
                local db, err = mysql:new()
                if not db then
                    ngx.say("实例化 mysql 失败: ", err)
                    return
                end

                db:set_timeout(1000) -- 1 秒

                -- 或连接到由 mysql 服务器监听的 unix 域套接字文件:
                --     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("连接失败: ", err, ": ", errcode, " ", sqlstate)
                    db:close()
                    return
                end

                ngx.say("已连接到 mysql.")

                local res, err, errcode, sqlstate =
                    db:query("如果存在则删除表 cats")
                if not res then
                    ngx.say("错误结果: ", err, ": ", errcode, ": ", sqlstate, ".")
                    db:close()
                    return
                end

                res, err, errcode, sqlstate =
                    db:query("创建表 cats "
                             .. "(id serial primary key, "
                             .. "name varchar(5))")
                if not res then
                    ngx.say("错误结果: ", err, ": ", errcode, ": ", sqlstate, ".")
                    db:close()
                    return
                end

                ngx.say("表 cats 已创建.")

                res, err, errcode, sqlstate =
                    db:query("插入到 cats (name) "
                             .. "值 (\'Bob\'),(\'\'),(null)")
                if not res then
                    ngx.say("错误结果: ", err, ": ", errcode, ": ", sqlstate, ".")
                    db:close()
                    return
                end

                ngx.say(res.affected_rows, " 行插入到表 cats ",
                        "(最后插入 id: ", res.insert_id, ")")

                -- 执行一个选择查询,预计结果集中约有 10 行:
                res, err, errcode, sqlstate =
                    db:query("选择 * 从 cats 按 id 升序", 10)
                if not res then
                    ngx.say("错误结果: ", err, ": ", errcode, ": ", sqlstate, ".")
                    db:close()
                    return
                end

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

                -- 将其放入大小为 100 的连接池中,
                -- 最大空闲超时为 10 秒
                local ok, err = db:set_keepalive(10000, 100)
                if not ok then
                    ngx.say("设置保持活动失败: ", err)
                    db:close()
                    return
                end

                -- 或立即关闭连接:
                -- local ok, err = db:close()
                -- if not ok then
                --     ngx.say("关闭失败: ", err)
                --     return
                -- end
            ';
        }
    }

方法

new

语法: db, err = mysql:new()

创建一个 MySQL 连接对象。如果失败,则返回 nil 和一个描述错误的字符串。

connect

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

尝试连接到远程 MySQL 服务器。

options 参数是一个 Lua 表,包含以下键:

  • host

    MySQL 服务器的主机名。 * port

    MySQL 服务器监听的端口。默认为 3306。 * path

    MySQL 服务器监听的 unix 套接字文件的路径。 * database

    MySQL 数据库名称。 * user

    登录的 MySQL 账户名。 * password

    登录的 MySQL 账户密码(明文)。 * charset

    用于 MySQL 连接的字符集,可以与默认字符集设置不同。 接受以下值:big5dec8cp850hp8koi8rlatin1latin2swe7asciiujissjishebrewtis620euckrkoi8ugb2312greekcp1250gbklatin5armscii8utf8ucs2cp866keybcs2maccemacromancp852latin7utf8mb4cp1251utf16utf16lecp1256cp1257utf32binarygeostd8cp932eucjpmsgb18030。 * max_packet_size

    从 MySQL 服务器发送的回复数据包的上限(默认为 1MB)。 * ssl

    如果设置为 true,则使用 SSL 连接到 MySQL(默认为 false)。如果 MySQL 服务器不支持 SSL (或仅被禁用),将返回错误字符串 "ssl disabled on server"。 * ssl_verify

    如果设置为 true,则验证服务器 SSL 证书的有效性(默认为 false)。 请注意,您需要配置 lua_ssl_trusted_certificate 来指定 MySQL 服务器使用的 CA(或服务器)证书。您可能还需要 相应地配置 lua_ssl_verify_depth。 * pool

    MySQL 连接池的名称。如果省略,将自动生成一个模糊的池名称,格式为 user:database:host:portuser:database:path。(此选项在 v0.08 中首次引入。)

  • pool_size

    指定连接池的大小。如果省略且未提供 backlog 选项,则不会创建池。如果省略但提供了 backlog,则池将以等于 lua_socket_pool_size 指令值的默认大小创建。连接池最多保持 pool_size 个活动连接,准备被后续调用 connect 重用,但请注意,池外打开的连接总数没有上限。如果您需要限制打开的连接总数,请指定 backlog 选项。当连接池超过其大小限制时,池中最少使用的(保持活动)连接将被关闭,以为当前连接腾出空间。请注意,cosocket 连接池是每个 Nginx 工作进程的,而不是每个 Nginx 服务器实例的,因此此处指定的大小限制也适用于每个 Nginx 工作进程。此外,请注意,一旦创建,连接池的大小无法更改。请注意,至少需要 ngx_lua 0.10.14 才能使用此选项。

  • backlog

    如果指定,此模块将限制此池的总打开连接数。此池在任何时候都不能打开超过 pool_size 的连接。如果连接池已满,后续连接操作将排队,队列长度等于此选项的值(“backlog”队列)。如果排队的连接操作数量等于 backlog,则后续连接操作将失败并返回 nil 以及错误字符串 "too many waiting connect operations"。一旦池中的连接数少于 pool_size,排队的连接操作将恢复。一旦排队超过 connect_timeout(由 set_timeout 控制),排队的连接操作将中止,并返回 nil 以及错误字符串 "timeout"。请注意,至少需要 ngx_lua 0.10.14 才能使用此选项。

  • compact_arrays

    当此选项设置为 true 时, queryread_result 方法将返回结果集的数组-数组结构,而不是默认的数组-哈希结构。

在实际解析主机名并连接到远程后端之前,此方法将始终查找连接池以匹配先前调用此方法创建的空闲连接。

set_timeout

语法: db:set_timeout(time)

设置后续操作的超时(以毫秒为单位)保护,包括 connect 方法。

set_keepalive

语法: ok, err = db:set_keepalive(max_idle_timeout, pool_size)

将当前 MySQL 连接立即放入 ngx_lua cosocket 连接池中。

您可以指定连接在池中的最大空闲超时(以毫秒为单位)和每个 NGINX 工作进程的池的最大大小。

如果成功,则返回 1。如果出现错误,则返回 nil 和一个描述错误的字符串。

仅在您本来会调用 close 方法的地方调用此方法。调用此方法将立即将当前 resty.mysql 对象转换为 closed 状态。对当前对象的任何后续操作(除了 connect())将返回 closed 错误。

get_reused_times

语法: times, err = db:get_reused_times()

此方法返回当前连接的(成功)重用次数。如果出错,则返回 nil 和一个描述错误的字符串。

如果当前连接不是来自内置连接池,则此方法始终返回 0,即连接从未被重用(尚未)。如果连接来自连接池,则返回值始终非零。因此,此方法也可用于确定当前连接是否来自池。

close

语法: ok, err = db:close()

关闭当前 MySQL 连接并返回状态。

如果成功,则返回 1。如果出现错误,则返回 nil 和一个描述错误的字符串。

send_query

语法: bytes, err = db:send_query(query)

将查询发送到远程 MySQL 服务器,而不等待其回复。

在成功时返回成功发送的字节数,否则返回 nil 和一个描述错误的字符串。

您应该使用 read_result 方法随后读取 MySQL 的回复。

read_result

语法: res, err, errcode, sqlstate = db:read_result()

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

读取从 MySQL 服务器返回的一个结果。

它返回一个 Lua 表 (res),描述 MySQL 的 OK packetresult set packet 的查询结果。

对于对应结果集的查询,它返回一个数组,包含所有行。每一行都包含每个数据字段的键值对。例如,

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

对于不对应结果集的查询,它返回一个类似于以下的 Lua 表:

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

如果当前结果后还有更多结果,则第二个 err 返回值将给出字符串 again。应始终检查此(第二个)返回值,如果为 again,则应再次调用此方法以检索更多结果。这通常发生在原始查询包含多个语句(在同一查询字符串中用分号分隔)或调用 MySQL 过程时。另请参见 Multi-Resultset Support

如果出错,此方法最多返回 4 个值:nilerrerrcodesqlstateerr 返回值包含描述错误的字符串,errcode 返回值保存 MySQL 错误代码(数值),最后,sqlstate 返回值包含由 5 个字符组成的标准 SQL 错误代码。请注意,如果 MySQL 不返回它们,errcodesqlstate 可能为 nil

可选参数 nrows 可用于指定结果集的近似行数。此值可用于在结果 Lua 表中预分配空间。默认值为 4。

query

语法: res, err, errcode, sqlstate = db:query(query)

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

这是将 send_query 调用和第一次 read_result 调用组合的快捷方式。

在成功的情况下,您应始终检查 err 返回值是否为 again,因为此方法仅会为您调用一次 read_result。另请参见 Multi-Resultset Support

server_ver

语法: str = db:server_ver()

返回 MySQL 服务器版本字符串,例如 "5.1.64"

您应仅在成功连接到 MySQL 服务器后调用此方法,否则将返回 nil

set_compact_arrays

语法: db:set_compact_arrays(boolean)

设置是否使用“紧凑数组”结构来返回后续查询的结果集。有关更多详细信息,请参见 connect 方法的 compact_arrays 选项。

此方法在 v0.09 版本中首次引入。

SQL 字面量引用

始终正确引用 SQL 字面量以防止 SQL 注入攻击非常重要。您可以使用 ngx_lua 提供的 ngx.quote_sql_str 函数来引用值。以下是一个示例:

    local name = ngx.unescape_uri(ngx.var.arg_name)
    local quoted_name = ngx.quote_sql_str(name)
    local sql = "选择 * 从 users 其中 name = " .. quoted_name

多结果集支持

对于产生多个结果集的 SQL 查询,您始终有责任检查 queryread_result 方法调用返回的 "again" 错误消息,并通过调用 read_result 方法不断提取更多结果集,直到不再返回 "again" 错误消息(或发生其他错误)。

以下是一个简单的示例:

    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, "连接失败: ", err, ": ", errcode, " ", sqlstate)
        return ngx.exit(500)
    end

    res, err, errcode, sqlstate = db:query("选择 1; 选择 2; 选择 3;")
    if not res then
        ngx.log(ngx.ERR, "错误结果 #1: ", err, ": ", errcode, ": ", sqlstate, ".")
        db:close()
        return ngx.exit(500)
    end

    ngx.say("结果 #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, "错误结果 #", i, ": ", err, ": ", errcode, ": ", sqlstate, ".")
            db:close()
            return ngx.exit(500)
        end

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

    local ok, err = db:set_keepalive(10000, 50)
    if not ok then
        ngx.log(ngx.ERR, "设置保持活动失败: ", err)
        db:close()
        ngx.exit(500)
    end

此代码片段将产生以下响应体数据:

结果 #1: [{"1":"1"}]
结果 #2: [{"2":"2"}]
结果 #3: [{"3":"3"}]

调试

通常使用 lua-cjson 库将 MySQL 查询方法的返回值编码为 JSON 是很方便的。例如,

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

自动错误日志记录

默认情况下,当发生套接字错误时,底层的 ngx_lua 模块会进行错误日志记录。如果您已经在自己的 Lua 代码中进行了适当的错误处理,则建议通过关闭 ngx_lualua_socket_log_errors 指令来禁用此自动错误日志记录,即,

    lua_socket_log_errors off;

限制

  • 此库不能在 init_by_lua、set_by_lua、log_by_lua 和 header_filter_by_lua 等代码上下文中使用,因为 ngx_lua cosocket API 不可用。
  • resty.mysql 对象实例不能在 Lua 模块级别存储在 Lua 变量中,因为它将被同一 NGINX 工作进程处理的所有并发请求共享(请参见 https://github.com/openresty/lua-nginx-module#data-sharing-within-an-nginx-worker),并导致在并发请求尝试使用同一 resty.mysql 实例时出现不良竞争条件。您应始终在函数局部变量或 ngx.ctx 表中初始化 resty.mysql 对象。这些地方都有各自的数据副本,适用于每个请求。

更多身份验证方法支持

默认情况下,所有身份验证方法中,仅支持 旧密码身份验证(mysql_old_password)安全密码身份验证(mysql_native_password)。如果服务器要求 sha256_password 或 cache_sha2_password,可能会返回类似 auth plugin caching_sha2_password or sha256_password are not supported because resty.rsa is not installed 的错误。

使用 sha256_passwordcache_sha2_password 时需要 lua-resty-rsa

另见

GitHub

您可以在 nginx-module-mysql 的 GitHub 仓库中找到此模块的其他配置提示和文档。