Перейти к содержанию

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

Чтобы использовать эту библиотеку Lua с NGINX, убедитесь, что nginx-module-lua установлен.

Этот документ описывает lua-resty-mysql v0.29, выпущенный 14 января 2026 года.


Эта библиотека Lua является клиентским драйвером MySQL для модуля ngx_lua nginx:

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

Эта библиотека Lua использует API cosocket ngx_lua, который обеспечивает 100% неблокирующее поведение.

Обратите внимание, что требуется как минимум ngx_lua 0.9.11 или ngx_openresty 1.7.4.1.

Также требуется библиотека bit. Если вы используете LuaJIT 2 с ngx_lua, то библиотека 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 секунда

                -- или подключитесь к файлу сокета unix,
                -- прослушиваемому сервером mysql:
                --     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, ")")

                -- выполняем запрос select, ожидается около 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("не удалось установить keepalive: ", err)
                    db:close()
                    return
                end

                -- или просто закройте соединение сразу:
                -- local ok, err = db:close()
                -- if not ok then
                --     ngx.say("не удалось закрыть: ", err)
                --     return
                -- end
            ';
        }
    }

Методы

new

syntax: db, err = mysql:new()

Создает объект соединения MySQL. В случае неудачи возвращает nil и строку, описывающую ошибку.

connect

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

Пытается подключиться к удаленному серверу MySQL.

Аргумент options — это таблица Lua, содержащая следующие ключи:

  • host

    имя хоста для сервера MySQL. * port

    порт, на котором прослушивает сервер MySQL. По умолчанию 3306. * path

    путь к файлу сокета unix, прослушиваемому сервером MySQL. * 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 (по умолчанию 1 МБ). * ssl

    Если установлено в true, то используется SSL для подключения к MySQL (по умолчанию false). Если сервер MySQL не поддерживает SSL (или просто отключен), будет возвращена строка ошибки "ssl disabled on server". * ssl_verify

    Если установлено в true, то проверяет действительность SSL-сертификата сервера (по умолчанию false). Обратите внимание, что вам необходимо настроить lua_ssl_trusted_certificate для указания сертификата CA (или сервера), используемого вашим сервером MySQL. Вам также может потребоваться настроить lua_ssl_verify_depth соответственно. * pool

    имя пула соединений MySQL. Если опущено, будет автоматически сгенерировано неоднозначное имя пула с помощью строкового шаблона user:database:host:port или user:database:path. (эта опция была впервые введена в v0.08.)

  • pool_size

    Указывает размер пула соединений. Если опущено и не была предоставлена опция backlog, пул не будет создан. Если опущено, но backlog была предоставлена, пул будет создан с размером по умолчанию, равным значению директивы lua_socket_pool_size. Пул соединений содержит до pool_size активных соединений, готовых к повторному использованию последующими вызовами к connect, но обратите внимание, что нет верхнего предела для общего числа открытых соединений вне пула. Если вам нужно ограничить общее количество открытых соединений, укажите опцию backlog. Когда пул соединений превысит свой лимит по размеру, будет закрыто наименее недавно использованное (keep-alive) соединение, уже находящееся в пуле, чтобы освободить место для текущего соединения. Обратите внимание, что пул соединений cosocket является на уровне каждого процесса рабочего Nginx, а не на уровне каждого экземпляра сервера Nginx, поэтому указанный здесь лимит размера также применяется к каждому отдельному процессу рабочего Nginx. Также обратите внимание, что размер пула соединений не может быть изменен после его создания. Обратите внимание, что требуется как минимум ngx_lua 0.10.14 для использования этих опций.

  • backlog

    Если указано, этот модуль ограничит общее количество открытых соединений для этого пула. Не более pool_size соединений может быть открыто для этого пула в любое время. Если пул соединений полон, последующие операции подключения будут помещены в очередь, равную значению этой опции (очередь "backlog"). Если количество ожидающих операций подключения равно backlog, последующие операции подключения завершатся неудачей и вернут nil плюс строку ошибки "слишком много ожидающих операций подключения". Ожидающие операции подключения будут возобновлены, как только количество соединений в пуле станет меньше pool_size. Ожидающая операция подключения будет прервана, как только она будет находиться в очереди более connect_timeout, управляемого set_timeout, и вернет nil плюс строку ошибки "timeout". Обратите внимание, что требуется как минимум ngx_lua 0.10.14 для использования этих опций.

  • compact_arrays

    когда эта опция установлена в true, методы query и read_result будут возвращать структуру массивов массивов для результирующего набора, а не структуру массивов хешей по умолчанию.

Перед фактическим разрешением имени хоста и подключением к удаленному бэкенду этот метод всегда будет искать в пуле соединений совпадающие неактивные соединения, созданные предыдущими вызовами этого метода.

set_timeout

syntax: db:set_timeout(time)

Устанавливает защиту таймаута (в мс) для последующих операций, включая метод connect.

set_keepalive

syntax: ok, err = db:set_keepalive(max_idle_timeout, pool_size)

Сразу помещает текущее соединение MySQL в пул соединений cosocket ngx_lua.

Вы можете указать максимальное время простоя (в мс), когда соединение находится в пуле, и максимальный размер пула для каждого рабочего процесса nginx.

В случае успеха возвращает 1. В случае ошибок возвращает nil с строкой, описывающей ошибку.

Вызывайте этот метод только в том месте, где вы бы вызвали метод close. Вызов этого метода немедленно переведет текущий объект resty.mysql в состояние closed. Любые последующие операции, кроме connect(), на текущем объекте вернут ошибку closed.

get_reused_times

syntax: times, err = db:get_reused_times()

Этот метод возвращает (успешно) количество раз, когда текущее соединение было повторно использовано. В случае ошибки возвращает nil и строку, описывающую ошибку.

Если текущее соединение не происходит из встроенного пула соединений, то этот метод всегда возвращает 0, то есть соединение никогда не было повторно использовано (пока). Если соединение происходит из пула соединений, то возвращаемое значение всегда ненулевое. Таким образом, этот метод также может использоваться для определения, происходит ли текущее соединение из пула.

close

syntax: ok, err = db:close()

Закрывает текущее соединение mysql и возвращает статус.

В случае успеха возвращает 1. В случае ошибок возвращает nil с строкой, описывающей ошибку.

send_query

syntax: bytes, err = db:send_query(query)

Отправляет запрос на удаленный сервер MySQL без ожидания его ответов.

Возвращает количество байт, успешно отправленных, в случае успеха, в противном случае возвращает nil и строку, описывающую ошибку.

Вы должны использовать метод read_result для чтения ответов MySQL после этого.

read_result

syntax: res, err, errcode, sqlstate = db:read_result()

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

Читает один результат, возвращенный сервером MySQL.

Он возвращает таблицу Lua (res), описывающую OK packet или result set packet MySQL для результата запроса.

Для запросов, соответствующих результирующему набору, он возвращает массив, содержащий все строки. Каждая строка содержит пары ключ-значение для каждого поля данных. Например,

    {
        { 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 содержит стандартный код ошибки SQL, состоящий из 5 символов. Обратите внимание, что errcode и sqlstate могут быть nil, если MySQL не возвращает их.

Необязательный аргумент nrows можно использовать для указания приблизительного количества строк для результирующего набора. Это значение можно использовать для предварительного выделения места в результирующей таблице Lua для результирующего набора. По умолчанию оно принимает значение 4.

query

syntax: res, err, errcode, sqlstate = db:query(query)

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

Это сокращение для объединения вызова send_query и первого вызова read_result.

Вы всегда должны проверять, если значение err равно again в случае успеха, потому что этот метод вызовет read_result только один раз для вас. См. также Поддержка нескольких результирующих наборов.

server_ver

syntax: str = db:server_ver()

Возвращает строку версии сервера MySQL, например, "5.1.64".

Вы должны вызывать этот метод только после успешного подключения к серверу MySQL, в противном случае будет возвращено nil.

set_compact_arrays

syntax: db:set_compact_arrays(boolean)

Устанавливает, использовать ли структуру "compact-arrays" для результирующих наборов, возвращаемых последующими запросами. См. опцию compact_arrays для метода connect для получения дополнительной информации.

Этот метод был впервые введен в релизе v0.09.

Цитирование SQL литералов

Всегда важно правильно цитировать SQL литералы, чтобы предотвратить атаки SQL-инъекций. Вы можете использовать функцию ngx.quote_sql_str, предоставляемую ngx_lua, для цитирования значений. Вот пример:

    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-запроса, который производит несколько результирующих наборов, всегда ваша обязанность проверять сообщение об ошибке "again", возвращаемое вызовами методов query или read_result, и продолжать извлекать больше результирующих наборов, вызывая метод 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, "не удалось установить keepalive: ", 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, то рекомендуется отключить эту автоматическую регистрацию ошибок, отключив директиву lua_socket_log_errors модуля ngx_lua, то есть,

    lua_socket_log_errors off;

Ограничения

  • Эта библиотека не может использоваться в контекстах кода, таких как init_by_lua, set_by_lua, log_by_lua, и header_filter_by_lua, где API cosocket ngx_lua недоступен.
  • Экземпляр объекта resty.mysql не может храниться в переменной Lua на уровне модуля Lua, поскольку он будет разделяться всеми параллельными запросами, обрабатываемыми одним и тем же рабочим процессом nginx (см. https://github.com/openresty/lua-nginx-module#data-sharing-within-an-nginx-worker) и приведет к плохим условиям гонки, когда параллельные запросы пытаются использовать один и тот же экземпляр resty.mysql. Вы всегда должны инициализировать объекты resty.mysql в локальных переменных функции или в таблице ngx.ctx. Эти места имеют свои собственные копии данных для каждого запроса.

Поддержка дополнительных методов аутентификации

По умолчанию из всех методов аутентификации поддерживаются только Старая аутентификация паролем (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.

Требуется lua-resty-rsa при использовании sha256_password и cache_sha2_password.

См. также

GitHub

Вы можете найти дополнительные советы по конфигурации и документацию для этого модуля в репозитории GitHub для nginx-module-mysql.