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.31,于 2026 年 6 月 07 日发布。
此 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 库。如果您在使用带有 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("drop table if exists cats")
if not res then
ngx.say("错误结果: ", 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("错误结果: ", err, ": ", errcode, ": ", sqlstate, ".")
db:close()
return
end
ngx.say("表 cats 创建成功.")
res, err, errcode, sqlstate =
db:query("insert into cats (name) "
.. "values (\'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("select * from cats order by id asc", 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 账户密码(明文)。 *
charsetMySQL 连接使用的字符集,可以与默认字符集设置不同。 接受以下值:
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 存储过程时。另请参见 多结果集支持。
如果出错,此方法最多返回 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 一次。另请参见 多结果集支持。
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 = "select * from users where 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("select 1; select 2; select 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("select * from 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。
对于 MariaDB 的 client_ed25519 身份验证插件(在许多最近的 MariaDB 部署中默认使用,自 MariaDB 10.1.22 起可用),需要 lua-resty-openssl。不需要额外的系统依赖 — 它重用了 OpenResty 已经链接的 libcrypto。每次签名的成本大约为 25 毫秒,按连接支付一次。
背景:此 MariaDB 插件中的 Ed25519 签名原语直接使用 SHA-512(password) 作为扩展的密钥,跳过 RFC 8032 种子派生步骤。因此,OpenSSL/libsodium 中的高级 Ed25519 API 不能直接使用。此驱动程序在 OpenSSL 的 BIGNUM 模块(resty.openssl.bn)之上实现了爱德华曲线点算术 — 字段/组算术通过 libcrypto 进行,而点加法和标量乘法则直接实现,以便我们可以输入 MariaDB 特定的扩展密钥。
另见
- 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 仓库 中找到此模块的其他配置提示和文档。