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