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.
См. также
- модуль 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.