Aller au contenu

mysql: Bibliothèque de pilote MySQL Lua non-bloquante pour nginx-module-lua

Installation

Si vous n'avez pas configuré l'abonnement au dépôt RPM, inscrivez-vous. Ensuite, vous pouvez procéder avec les étapes suivantes.

CentOS/RHEL 7 ou 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

Pour utiliser cette bibliothèque Lua avec NGINX, assurez-vous que nginx-module-lua est installé.

Ce document décrit lua-resty-mysql v0.29 publié le 14 janvier 2026.


Cette bibliothèque Lua est un pilote client MySQL pour le module ngx_lua de nginx :

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

Cette bibliothèque Lua tire parti de l'API cosocket de ngx_lua, qui garantit un comportement 100 % non-bloquant.

Notez qu'au moins ngx_lua 0.9.11 ou ngx_openresty 1.7.4.1 est requis.

De plus, la bibliothèque bit est également requise. Si vous utilisez LuaJIT 2 avec ngx_lua, alors la bibliothèque bit est déjà disponible par défaut.

Synopsis

    # vous n'avez pas besoin de la ligne suivante si vous utilisez
    # le bundle ngx_openresty :
    server {
        location /test {
            content_by_lua '
                local mysql = require "resty.mysql"
                local db, err = mysql:new()
                if not db then
                    ngx.say("échec de l'instanciation de mysql : ", err)
                    return
                end

                db:set_timeout(1000) -- 1 sec

                -- ou connectez-vous à un fichier de socket de domaine unix écouté
                -- par un serveur 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("échec de la connexion : ", err, ": ", errcode, " ", sqlstate)
                    db:close()
                    return
                end

                ngx.say("connecté à mysql.")

                local res, err, errcode, sqlstate =
                    db:query("drop table if exists cats")
                if not res then
                    ngx.say("mauvais résultat : ", 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("mauvais résultat : ", err, ": ", errcode, ": ", sqlstate, ".")
                    db:close()
                    return
                end

                ngx.say("table cats créée.")

                res, err, errcode, sqlstate =
                    db:query("insert into cats (name) "
                             .. "values (\'Bob\'),(\'\'),(null)")
                if not res then
                    ngx.say("mauvais résultat : ", err, ": ", errcode, ": ", sqlstate, ".")
                    db:close()
                    return
                end

                ngx.say(res.affected_rows, " lignes insérées dans la table cats ",
                        "(dernier id inséré : ", res.insert_id, ")")

                -- exécutez une requête select, environ 10 lignes attendues dans
                -- l'ensemble de résultats :
                res, err, errcode, sqlstate =
                    db:query("select * from cats order by id asc", 10)
                if not res then
                    ngx.say("mauvais résultat : ", err, ": ", errcode, ": ", sqlstate, ".")
                    db:close()
                    return
                end

                local cjson = require "cjson"
                ngx.say("résultat : ", cjson.encode(res))

                -- mettez-le dans le pool de connexions de taille 100,
                -- avec un délai d'inactivité maximal de 10 secondes
                local ok, err = db:set_keepalive(10000, 100)
                if not ok then
                    ngx.say("échec de la mise en attente : ", err)
                    db:close()
                    return
                end

                -- ou fermez simplement la connexion immédiatement :
                -- local ok, err = db:close()
                -- if not ok then
                --     ngx.say("échec de la fermeture : ", err)
                --     return
                -- end
            ';
        }
    }

Méthodes

new

syntax: db, err = mysql:new()

Crée un objet de connexion MySQL. En cas d'échec, retourne nil et une chaîne décrivant l'erreur.

connect

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

Tente de se connecter au serveur MySQL distant.

L'argument options est une table Lua contenant les clés suivantes :

  • host

    le nom d'hôte pour le serveur MySQL. * port

    le port sur lequel le serveur MySQL écoute. Par défaut 3306. * path

    le chemin du fichier de socket unix écouté par le serveur MySQL. * database

    le nom de la base de données MySQL. * user

    le nom du compte MySQL pour la connexion. * password

    le mot de passe du compte MySQL pour la connexion (en texte clair). * charset

    le jeu de caractères utilisé sur la connexion MySQL, qui peut être différent du paramètre de jeu de caractères par défaut. Les valeurs suivantes sont acceptées : 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

    la limite supérieure pour les paquets de réponse envoyés par le serveur MySQL (par défaut 1 Mo). * ssl

    Si défini sur true, utilise SSL pour se connecter à MySQL (par défaut false). Si le serveur MySQL n'a pas de support SSL (ou est simplement désactivé), la chaîne d'erreur "ssl désactivé sur le serveur" sera retournée. * ssl_verify

    Si défini sur true, vérifie la validité du certificat SSL du serveur (par défaut false). Notez que vous devez configurer le lua_ssl_trusted_certificate pour spécifier le certificat CA (ou serveur) utilisé par votre serveur MySQL. Vous devrez peut-être également configurer lua_ssl_verify_depth en conséquence. * pool

    le nom du pool de connexions MySQL. s'il est omis, un nom de pool ambigu sera généré automatiquement avec le modèle de chaîne user:database:host:port ou user:database:path. (cette option a été introduite pour la première fois dans v0.08.)

  • pool_size

    Spécifie la taille du pool de connexions. Si omis et qu'aucune option backlog n'a été fournie, aucun pool ne sera créé. Si omis mais que backlog a été fourni, le pool sera créé avec une taille par défaut égale à la valeur de la directive lua_socket_pool_size. Le pool de connexions conserve jusqu'à pool_size connexions vivantes prêtes à être réutilisées par des appels ultérieurs à connect, mais notez qu'il n'y a pas de limite supérieure au nombre total de connexions ouvertes en dehors du pool. Si vous devez restreindre le nombre total de connexions ouvertes, spécifiez l'option backlog. Lorsque le pool de connexions dépasserait sa limite de taille, la connexion la moins récemment utilisée (gardée en vie) déjà dans le pool sera fermée pour faire de la place pour la connexion actuelle. Notez que le pool de connexions cosocket est par processus de travail Nginx plutôt que par instance de serveur Nginx, donc la limite de taille spécifiée ici s'applique également à chaque processus de travail Nginx. Notez également que la taille du pool de connexions ne peut pas être modifiée une fois qu'il a été créé. Notez qu'au moins ngx_lua 0.10.14 est requis pour utiliser ces options.

  • backlog

    Si spécifié, ce module limitera le nombre total de connexions ouvertes pour ce pool. Aucun nombre de connexions supérieur à pool_size ne peut être ouvert pour ce pool à tout moment. Si le pool de connexions est plein, les opérations de connexion ultérieures seront mises en file d'attente dans une file d'attente égale à la valeur de cette option (la file d'attente "backlog"). Si le nombre d'opérations de connexion mises en file d'attente est égal à backlog, les opérations de connexion ultérieures échoueront et retourneront nil plus la chaîne d'erreur "trop d'opérations de connexion en attente". Les opérations de connexion mises en file d'attente seront reprises une fois que le nombre de connexions dans le pool est inférieur à pool_size. L'opération de connexion mise en file d'attente sera abandonnée une fois qu'elle a été mise en file d'attente pendant plus de connect_timeout, contrôlé par set_timeout, et renverra nil plus la chaîne d'erreur "timeout". Notez qu'au moins ngx_lua 0.10.14 est requis pour utiliser ces options.

  • compact_arrays

    lorsque cette option est définie sur true, alors les méthodes query et read_result retourneront la structure de tableau de tableaux pour l'ensemble de résultats, plutôt que la structure de tableau de hachages par défaut.

Avant de résoudre réellement le nom d'hôte et de se connecter au backend distant, cette méthode recherchera toujours le pool de connexions pour des connexions inactives correspondantes créées par des appels précédents de cette méthode.

set_timeout

syntax: db:set_timeout(time)

Définit le délai d'expiration (en ms) pour les opérations ultérieures, y compris la méthode connect.

set_keepalive

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

Met immédiatement la connexion MySQL actuelle dans le pool de connexions cosocket ngx_lua.

Vous pouvez spécifier le délai d'inactivité maximal (en ms) lorsque la connexion est dans le pool et la taille maximale du pool pour chaque processus de travail nginx.

En cas de succès, retourne 1. En cas d'erreurs, retourne nil avec une chaîne décrivant l'erreur.

N'appelez cette méthode que là où vous auriez appelé la méthode close à la place. L'appel de cette méthode mettra immédiatement l'objet resty.mysql actuel dans l'état closed. Toute opération ultérieure autre que connect() sur l'objet actuel retournera l'erreur closed.

get_reused_times

syntax: times, err = db:get_reused_times()

Cette méthode retourne le nombre de fois (avec succès) que la connexion actuelle a été réutilisée. En cas d'erreur, elle retourne nil et une chaîne décrivant l'erreur.

Si la connexion actuelle ne provient pas du pool de connexions intégré, alors cette méthode retourne toujours 0, c'est-à-dire que la connexion n'a jamais été réutilisée (jusqu'à présent). Si la connexion provient du pool de connexions, alors la valeur de retour est toujours non nulle. Donc, cette méthode peut également être utilisée pour déterminer si la connexion actuelle provient du pool.

close

syntax: ok, err = db:close()

Ferme la connexion mysql actuelle et retourne le statut.

En cas de succès, retourne 1. En cas d'erreurs, retourne nil avec une chaîne décrivant l'erreur.

send_query

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

Envoie la requête au serveur MySQL distant sans attendre ses réponses.

Retourne les octets envoyés avec succès en cas de succès et sinon retourne nil et une chaîne décrivant l'erreur.

Vous devez utiliser la méthode read_result pour lire les réponses MySQL par la suite.

read_result

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

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

Lit un résultat retourné par le serveur MySQL.

Elle retourne une table Lua (res) décrivant le OK packet ou le result set packet de MySQL pour le résultat de la requête.

Pour les requêtes correspondant à un ensemble de résultats, elle retourne un tableau contenant toutes les lignes. Chaque ligne contient des paires clé-valeur pour chaque champ de données. Par exemple,

    {
        { name = "Bob", age = 32, phone = ngx.null },
        { name = "Marry", age = 18, phone = "10666372"}
    }

Pour les requêtes qui ne correspondent pas à un ensemble de résultats, elle retourne une table Lua comme ceci :

    {
        insert_id = 0,
        server_status = 2,
        warning_count = 1,
        affected_rows = 32,
        message = nil
    }

Si d'autres résultats suivent le résultat actuel, une seconde valeur de retour err sera donnée avec la chaîne again. On doit toujours vérifier cette (deuxième) valeur de retour et si elle est again, alors elle doit appeler cette méthode à nouveau pour récupérer plus de résultats. Cela se produit généralement lorsque la requête d'origine contient plusieurs instructions (séparées par un point-virgule dans la même chaîne de requête) ou appelle une procédure MySQL. Voir aussi Support Multi-Resultset.

En cas d'erreurs, cette méthode retourne au maximum 4 valeurs : nil, err, errcode, et sqlstate. La valeur de retour err contient une chaîne décrivant l'erreur, la valeur de retour errcode contient le code d'erreur MySQL (une valeur numérique), et enfin, la valeur de retour sqlstate contient le code d'erreur SQL standard qui se compose de 5 caractères. Notez que, errcode et sqlstate peuvent être nil si MySQL ne les retourne pas.

L'argument optionnel nrows peut être utilisé pour spécifier un nombre approximatif de lignes pour l'ensemble de résultats. Cette valeur peut être utilisée pour pré-allouer de l'espace dans la table Lua résultante pour l'ensemble de résultats. Par défaut, elle prend la valeur 4.

query

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

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

Ceci est un raccourci pour combiner l'appel send_query et le premier appel read_result.

Vous devez toujours vérifier si la valeur de retour err est again en cas de succès car cette méthode n'appellera read_result qu'une seule fois pour vous. Voir aussi Support Multi-Resultset.

server_ver

syntax: str = db:server_ver()

Retourne la chaîne de version du serveur MySQL, comme "5.1.64".

Vous ne devez appeler cette méthode qu'après vous être connecté avec succès à un serveur MySQL, sinon nil sera retourné.

set_compact_arrays

syntax: db:set_compact_arrays(boolean)

Définit si utiliser la structure "compact-arrays" pour les ensembles de résultats retournés par les requêtes ultérieures. Voir l'option compact_arrays pour la méthode connect pour plus de détails.

Cette méthode a été introduite pour la première fois dans la version v0.09.

Citation Littérale SQL

Il est toujours important de citer correctement les littéraux SQL pour prévenir les attaques par injection SQL. Vous pouvez utiliser la fonction ngx.quote_sql_str fournie par ngx_lua pour citer les valeurs. Voici un exemple :

    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

Support Multi-Resultset

Pour une requête SQL qui produit plusieurs ensembles de résultats, il est toujours de votre devoir de vérifier le message d'erreur "again" retourné par les appels de méthode query ou read_result, et de continuer à récupérer plus d'ensembles de résultats en appelant la méthode read_result jusqu'à ce qu'aucun message d'erreur "again" ne soit retourné (ou que d'autres erreurs se produisent).

Voici un exemple trivial pour cela :

    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, "échec de la connexion : ", 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, "mauvais résultat #1 : ", err, ": ", errcode, ": ", sqlstate, ".")
        db:close()
        return ngx.exit(500)
    end

    ngx.say("résultat #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, "mauvais résultat #", i, ": ", err, ": ", errcode, ": ", sqlstate, ".")
            db:close()
            return ngx.exit(500)
        end

        ngx.say("résultat #", i, ": ", cjson.encode(res))
        i = i + 1
    end

    local ok, err = db:set_keepalive(10000, 50)
    if not ok then
        ngx.log(ngx.ERR, "échec de la mise en attente : ", err)
        db:close()
        ngx.exit(500)
    end

Ce snippet de code produira les données de corps de réponse suivantes :

résultat #1 : [{"1":"1"}]
résultat #2 : [{"2":"2"}]
résultat #3 : [{"3":"3"}]

Débogage

Il est généralement pratique d'utiliser la bibliothèque lua-cjson pour encoder les valeurs de retour des méthodes de requête MySQL en JSON. Par exemple,

    local cjson = require "cjson"
    ...
    local res, err, errcode, sqlstate = db:query("select * from cats")
    if res then
        print("res : ", cjson.encode(res))
    end

Journalisation Automatique des Erreurs

Par défaut, le module sous-jacent ngx_lua effectue une journalisation des erreurs lorsque des erreurs de socket se produisent. Si vous gérez déjà correctement les erreurs dans votre propre code Lua, il est recommandé de désactiver cette journalisation automatique des erreurs en désactivant la directive lua_socket_log_errors de ngx_lua, c'est-à-dire,

    lua_socket_log_errors off;

Limitations

  • Cette bibliothèque ne peut pas être utilisée dans des contextes de code comme init_by_lua, set_by_lua, log_by_lua, et header_filter_by_lua où l'API cosocket ngx_lua n'est pas disponible.
  • L'instance d'objet resty.mysql ne peut pas être stockée dans une variable Lua au niveau du module Lua, car elle sera alors partagée par toutes les requêtes concurrentes traitées par le même processus de travail nginx (voir https://github.com/openresty/lua-nginx-module#data-sharing-within-an-nginx-worker ) et résulter en de mauvaises conditions de concurrence lorsque des requêtes concurrentes essaient d'utiliser la même instance resty.mysql. Vous devez toujours initier des objets resty.mysql dans des variables locales de fonction ou dans la table ngx.ctx. Ces endroits ont tous leurs propres copies de données pour chaque requête.

Plus de Support pour les Méthodes d'Authentification

Par défaut, parmi toutes les méthodes d'authentification, seules Old Password Authentication(mysql_old_password) et Secure Password Authentication(mysql_native_password) sont supportées. Si le serveur nécessite sha256_password ou cache_sha2_password, une erreur comme auth plugin caching_sha2_password or sha256_password are not supported because resty.rsa is not installed peut être retournée.

Besoin de lua-resty-rsa lors de l'utilisation de sha256_password et cache_sha2_password.

Voir Aussi

GitHub

Vous pouvez trouver des conseils de configuration supplémentaires et de la documentation pour ce module dans le dépôt GitHub pour nginx-module-mysql.