lua-resty-mysql
基于 cosocket API 的 ngx_lua Lua MySQL 客户端驱动
$ opm get openresty/lua-resty-mysql
名称
lua-resty-mysql - 基于 cosocket API 的 ngx_lua Lua MySQL 客户端驱动
状态
该库被认为已准备好用于生产环境。
描述
此 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 库。如果您使用 LuaJIT 2 与 ngx_lua,则默认情况下 bit
库已可用。
概要
# you do not need the following line if you are using
# the ngx_openresty bundle:
lua_package_path "/path/to/lua-resty-mysql/lib/?.lua;;";
server {
location /test {
content_by_lua '
local mysql = require "resty.mysql"
local db, err = mysql:new()
if not db then
ngx.say("failed to instantiate mysql: ", err)
return
end
db:set_timeout(1000) -- 1 sec
-- or connect to a unix domain socket file listened
-- by a mysql server:
-- 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("failed to connect: ", err, ": ", errcode, " ", sqlstate)
return
end
ngx.say("connected to mysql.")
local res, err, errcode, sqlstate =
db:query("drop table if exists cats")
if not res then
ngx.say("bad result: ", err, ": ", errcode, ": ", sqlstate, ".")
return
end
res, err, errcode, sqlstate =
db:query("create table cats "
.. "(id serial primary key, "
.. "name varchar(5))")
if not res then
ngx.say("bad result: ", err, ": ", errcode, ": ", sqlstate, ".")
return
end
ngx.say("table cats created.")
res, err, errcode, sqlstate =
db:query("insert into cats (name) "
.. "values (\'Bob\'),(\'\'),(null)")
if not res then
ngx.say("bad result: ", err, ": ", errcode, ": ", sqlstate, ".")
return
end
ngx.say(res.affected_rows, " rows inserted into table cats ",
"(last insert id: ", res.insert_id, ")")
-- run a select query, expected about 10 rows in
-- the result set:
res, err, errcode, sqlstate =
db:query("select * from cats order by id asc", 10)
if not res then
ngx.say("bad result: ", err, ": ", errcode, ": ", sqlstate, ".")
return
end
local cjson = require "cjson"
ngx.say("result: ", cjson.encode(res))
-- put it into the connection pool of size 100,
-- with 10 seconds max idle timeout
local ok, err = db:set_keepalive(10000, 100)
if not ok then
ngx.say("failed to set keepalive: ", err)
return
end
-- or just close the connection right away:
-- local ok, err = db:close()
-- if not ok then
-- ngx.say("failed to close: ", 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 连接上使用的字符集,可以与默认字符集设置不同。接受以下值:
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。pool
MySQL 连接池的名称。如果省略,则会使用字符串模板
user:database:host:port
或user:database:path
自动生成一个模糊的池名称。(此选项首次在v0.08
中引入)。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 数据包
或 结果集数据包
。
对于对应于结果集的查询,它返回一个包含所有行的数组。每行都包含每个数据字段的键值对。例如,
{
{ 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, "failed to connect: ", 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, "bad result #1: ", err, ": ", errcode, ": ", sqlstate, ".")
return ngx.exit(500)
end
ngx.say("result #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, "bad result #", i, ": ", err, ": ", errcode, ": ", sqlstate, ".")
return ngx.exit(500)
end
ngx.say("result #", i, ": ", cjson.encode(res))
i = i + 1
end
local ok, err = db:set_keepalive(10000, 50)
if not ok then
ngx.log(ngx.ERR, "failed to set keepalive: ", err)
ngx.exit(500)
end
此代码段将生成以下响应主体数据
result #1: [{"1":"1"}]
result #2: [{"2":"2"}]
result #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
对象。这些位置都为每个请求拥有自己的数据副本。
安装
如果您使用的是 ngx_openresty 包(https://openresty.org.cn),则您无需执行任何操作,因为它默认已包含并启用了 lua-resty-mysql。您只需在 Lua 代码中使用它,例如
local mysql = require "resty.mysql"
...
如果您使用的是自己的 nginx + ngx_lua 构建,则需要配置 lua_package_path 指令,将 lua-resty-mysql 源代码树的路径添加到 ngx_lua 的 LUA_PATH 搜索路径,例如
# nginx.conf
http {
lua_package_path "/path/to/lua-resty-mysql/lib/?.lua;;";
...
}
确保运行 Nginx “worker” 进程的系统帐户具有读取 .lua
文件的足够权限。
社区
英文邮件列表
openresty-en 邮件列表供英语使用者使用。
中文邮件列表
此 OpenResty 邮件列表 供中文用户使用。
Bug 和补丁
请通过以下方式提交 Bug 报告、愿望清单或补丁:
在 GitHub Issue Tracker 上创建工单,
或发布到 OpenResty 社区。
待办事项
改进 MySQL 连接池支持。
实现 MySQL 二进制行数据包。
实现 MySQL 4.0 之前的旧版身份验证方法。
实现 MySQL 服务器的 prepare 和 execute 数据包。
在协议中实现数据压缩支持。
作者
章亦春 (agentzh) <[email protected]>,OpenResty Inc.
版权和许可
此模块根据 BSD 许可证授权。
版权所有 (C) 2012-2018,由章亦春 (agentzh) <[email protected]>,OpenResty Inc. 所有。
保留所有权利。
在满足以下条件的情况下,允许以源代码和二进制形式重新分发和使用,无论是否修改:
源代码的重新分发必须保留上述版权声明、此条件列表以及以下免责声明。
二进制形式的重新分发必须在随分发提供的文档和/或其他材料中复制上述版权声明、此条件列表以及以下免责声明。
本软件由版权持有人和贡献者“按原样”提供,并且不提供任何明示或暗示的保证,包括但不限于适销性和特定用途适用性的暗示保证。在任何情况下,版权持有人或贡献者均不对任何直接、间接、附带、特殊、惩罚性或后果性损害(包括但不限于替代商品或服务的采购;使用、数据或利润损失;或业务中断)负责,无论此类损害是如何造成的以及基于何种责任理论,无论是合同、严格责任还是侵权行为(包括疏忽或其他原因),即使已被告知可能发生此类损害。
另请参阅
ngx_lua 模块:https://github.com/openresty/lua-nginx-module
MySQL 有线协议规范:http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol
the lua-resty-memcached 库
the lua-resty-redis 库
ngx_drizzle 模块:https://github.com/openresty/drizzle-nginx-module
作者
章亦春 (agentzh)
许可证
2bsd
依赖项
luajit >= 2.1.0, ngx_http_lua >= 0.10.6
版本
-
基于 cosocket API 的 ngx_lua 的 Lua MySQL 客户端驱动程序 2020-04-03 03:49:10
-
基于 cosocket API 的 ngx_lua 的 Lua MySQL 客户端驱动程序 2018-04-19 20:46:38
-
基于 cosocket API 的 ngx_lua 的 Lua MySQL 客户端驱动程序 2017-08-08 22:07:01
-
基于 cosocket API 的 ngx_lua 的 Lua MySQL 客户端驱动程序 2017-04-08 22:27:39
-
基于 cosocket API 的 ngx_lua 的 Lua MySQL 客户端驱动程序 2016-09-29 03:16:38
-
2016-09-29 03:11:56
-
2016-09-29 03:11:23
-
2016-09-29 03:10:25