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.11ngx_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 连接上使用的字符集,可以与默认字符集设置不同。接受以下值: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 中引入)。

  • 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 数据包结果集数据包

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

        {
            { 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 个值: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 一次。另请参阅 “多结果集支持”

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 查询,始终是您的职责检查 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, "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_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 对象。这些位置都为每个请求拥有自己的数据副本。

安装

如果您使用的是 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 报告、愿望清单或补丁:

  1. GitHub Issue Tracker 上创建工单,

  2. 或发布到 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

版本