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.11 或 ngx_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 表,包含以下键:
-
hostMySQL 服务器的主机名。 *
portMySQL 服务器监听的端口。默认为 3306。 *
pathMySQL 服务器监听的 unix 套接字文件的路径。 *
databaseMySQL 数据库名称。 *
user登录的 MySQL 账户名。 *
password登录的 MySQL 账户密码(明文)。 *
charset用于 MySQL 连接的字符集,可以与默认字符集设置不同。 接受以下值:
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从 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。 *poolMySQL 连接池的名称。如果省略,将自动生成一个模糊的池名称,格式为
user:database:host:port或user: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 时, query 和 read_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 packet 或 result 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 个值:nil、err、errcode 和 sqlstate。err 返回值包含描述错误的字符串,errcode 返回值保存 MySQL 错误代码(数值),最后,sqlstate 返回值包含由 5 个字符组成的标准 SQL 错误代码。请注意,如果 MySQL 不返回它们,errcode 和 sqlstate 可能为 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 查询,您始终有责任检查 query 或 read_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_lua 的 lua_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_password 和 cache_sha2_password 时需要 lua-resty-rsa。
另见
- ngx_lua 模块: https://github.com/openresty/lua-nginx-module
- MySQL 有线协议规范: http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol
- lua-resty-memcached 库
- lua-resty-redis 库
- ngx_drizzle 模块: https://github.com/openresty/drizzle-nginx-module
GitHub
您可以在 nginx-module-mysql 的 GitHub 仓库中找到此模块的其他配置提示和文档。