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

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.31, выпущенный 7 июня 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

синтаксис: db, err = mysql:new()

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

connect

синтаксис: 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. Когда пул соединений превышает свой лимит размера, будет закрыто наименее недавно использованное (поддерживаемое) соединение, уже находящееся в пуле, чтобы освободить место для текущего соединения. Обратите внимание, что пул соединений cosocket является пер-process 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

синтаксис: db:set_timeout(time)

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

set_keepalive

синтаксис: 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

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

Необязательный аргумент 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)

Устанавливает, следует ли использовать структуру "компактных массивов" для результирующих наборов, возвращаемых последующими запросами. Смотрите опцию 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.

Для плагина аутентификации client_ed25519 MariaDB (по умолчанию во многих недавних развертываниях MariaDB, доступен с MariaDB 10.1.22) требуется lua-resty-openssl. Не требуется дополнительных системных зависимостей — он повторно использует libcrypto, с которым уже связывается OpenResty. Стоимость составляет примерно 25 мс на подпись, оплачивается один раз за соединение.

Фон: примитив подписи Ed25519 в этом плагине MariaDB использует SHA-512(password) напрямую в качестве расширенного секретного ключа, пропуская этап деривации семян RFC 8032. В результате высокоуровневые API Ed25519 в OpenSSL/libsodium не могут быть использованы как есть. Этот драйвер реализует арифметику точек на кривой Эдвардса на основе модуля BIGNUM OpenSSL (resty.openssl.bn) — арифметика полей/групп проходит через libcrypto, в то время как сложение точек и скалярное умножение реализованы непосредственно, чтобы мы могли передать расширенный секрет, специфичный для MariaDB.

См. также

GitHub

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