Qu’est-ce que MySQL ?
MySQL est une base de données relationnelle open-source qui est devenue la couche de stockage par défaut des débuts du web. Lancé en 1995 avec un accent mis sur la rapidité et la simplicité pour les sites avec une forte charge de lecture, il s’est développé aux côtés de la pile LAMP — Linux, Apache, MySQL et PHP — pour devenir l’une des bases de données les plus déployées au monde.
Oracle en assure désormais le développement, mais une vaste communauté et un riche ensemble de services managés permettent son omniprésence. WordPress, Magento, les plateformes de commerce type Shopify et d’innombrables applications sur mesure fonctionnent sous MySQL. Si vous avez utilisé un site web avec un formulaire de connexion, il y a de fortes chances qu’une table MySQL soit intervenue.
Le MySQL moderne n’est plus le moteur simple des années 1990. Depuis la version 8, il dispose d’un dictionnaire de données transactionnel, d’expressions de table communes (CTE), de fonctions de fenêtrage et d’un support performant du JSON. L’apprendre aujourd’hui, c’est apprendre une base de données relationnelle sérieuse qui bénéficie, par ailleurs, d’un support extraordinaire.
Où MySQL est-il déployé ?
Le plus grand avantage pratique de MySQL est son ubiquité. Presque tous les hébergeurs mutualisés, fournisseurs cloud et plateformes-as-a-service le proposent. Les frameworks fournissent des drivers, les ORM le supportent nativement et les DBA possèdent des décennies d’expérience avec cet outil. Cela réduit le coût de tout ce qui gravite autour de la base de données : le recrutement, l’outillage, le monitoring et la migration.
C’est le choix habituel pour les systèmes de gestion de contenu, l’e-commerce, les backends SaaS et toute application où l’écosystème importe autant que le moteur. Il s’adapte aussi bien à une seule petite instance qu’à des clusters shardés, et le chemin pour passer de l’un à l’autre est parfaitement documenté.
InnoDB est le moteur qui compte
MySQL possède une architecture de moteur de stockage interchangeable, mais en pratique, vous utiliserez InnoDB. Il offre :
- Des transactions ACID avec
COMMITetROLLBACK. - Le verrouillage au niveau de la ligne (row-level locking), pour que les écritures ne bloquent pas les lectures.
- Des clés étrangères et l’application de contraintes.
- La récupération après plantage (crash recovery) via un journal de redo.
- Le MVCC pour des lectures cohérentes.
L’ancien moteur MyISAM ne gérait ni les transactions ni le verrouillage au niveau de la ligne. Il n’a plus sa place dans un nouveau schéma. Déclarez toujours ENGINE=InnoDB explicitement afin que le choix soit visible et ne dépende pas des configurations par défaut du serveur.
CREATE TABLE products (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
sku VARCHAR(64) NOT NULL,
name VARCHAR(200) NOT NULL,
price_cents INT UNSIGNED NOT NULL,
stock INT NOT NULL DEFAULT 0,
attributes JSON NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uniq_products_sku (sku)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Types de données et AUTO_INCREMENT
Le système de types de MySQL est pragmatique. Voici les choix les plus courants :
INTetBIGINT— nombres entiers ; ajoutezUNSIGNEDpour les IDs et les compteurs qui ne peuvent pas être négatifs.VARCHAR(n)— texte de longueur variable avec un maximum. Contrairement à Postgres, MySQL bénéficie réellement d’une longueur définie judicieusement.TEXT— textes volumineux stockés en dehors de la ligne si nécessaire.DECIMAL(p, s)— décimales exactes, le type correct pour l’argent si vous n’utilisez pas d’unités mineures en entiers.TIMESTAMPetDATETIME— horodatages ;TIMESTAMPconvertit en UTC et possède une limite de plage, tandis queDATETIMEstocke exactement ce que vous lui donnez.JSON— un document JSON validé et stocké efficacement.ENUM— un ensemble fixe de chaînes de caractères ; pratique, mais modifier la liste nécessite un changement de schéma.
AUTO_INCREMENT génère l’entier suivant pour une colonne, presque toujours la clé primaire. C’est rapide et tolérant aux écarts : les insertions annulées (rolled-back) consomment une valeur, et les insertions concurrentes peuvent ne pas produire de numéros contigus. Ne vous fiez jamais au fait que l’ID soit sans interruption ou que son ordre ait une signification particulière.
INSERT INTO products (sku, name, price_cents, stock, attributes)
VALUES ('SKU-1', 'Widget', 999, 10, JSON_OBJECT('colour', 'blue'));
SELECT LAST_INSERT_ID();
Le CRUD sans surprises
Les quatre opérations de base correspondent à quatre instructions. Lisez-les comme un ensemble, car la structure se répète.
INSERT INTO products (sku, name, price_cents, stock, attributes)
VALUES ('SKU-2', 'Gadget', 1499, 5, JSON_OBJECT('colour', 'red'));
SELECT id, sku, name, price_cents
FROM products
WHERE stock > 0
ORDER BY price_cents ASC
LIMIT 20 OFFSET 40;
UPDATE products
SET price_cents = 1299, stock = stock - 1
WHERE id = 42;
DELETE FROM products
WHERE stock = 0 AND created_at < NOW() - INTERVAL 90 DAY;
Deux bonnes habitudes permettent d’éviter les erreurs classiques. Premièrement, incluez toujours une clause WHERE pour UPDATE et DELETE ; sans elle, toutes les lignes sont modifiées. Deuxièmement, exécutez d’abord l’équivalent SELECT pour confirmer quelles lignes vous allez affecter. MySQL dispose d’un mode sql_safe_updates qui refuse les instructions sans clé dans le WHERE, et l’activer en développement est une sécurité simple et efficace.
LIMIT avec OFFSET permet la pagination, mais les offsets importants scannent et rejettent des lignes. Pour une pagination profonde, privilégiez la pagination par clés (keyset pagination) : WHERE id > :last_id ORDER BY id LIMIT 20.
Joins et agrégation
Les joins combinent des tables sur une condition de correspondance, exactement comme en SQL standard.
SELECT c.name AS category,
COUNT(*) AS product_count,
SUM(p.price_cents) AS inventory_value
FROM products p
JOIN categories c ON c.id = p.category_id
WHERE p.stock > 0
GROUP BY c.id, c.name
ORDER BY inventory_value DESC
LIMIT 20;
JOIN conserve les paires correspondantes, LEFT JOIN conserve toutes les lignes de gauche et remplit les colonnes de droite manquantes avec NULL. Les agrégats tels que COUNT, SUM, AVG, MIN et MAX regroupent les données ; chaque colonne sélectionnée non agrégée doit être groupée.
Historiquement, MySQL permettait de sélectionner des colonnes non groupées et renvoyait une valeur arbitraire, ce qui masquait des bugs. Avec ONLY_FULL_GROUP_BY activé — le réglage par défaut dans MySQL 8 — le serveur rejette les requêtes ambiguës, ce qui est le comportement souhaité. Ne le désactivez pas pour faire fonctionner une ancienne requête ; corrigez la requête.
WHERE filtre les lignes avant le regroupement et HAVING filtre après, donc les conditions sur les agrégats doivent se trouver dans HAVING.
Index et EXPLAIN
Un index est une structure triée qui permet d’éviter un scan complet de la table. MySQL en crée automatiquement un pour la clé primaire et pour chaque contrainte UNIQUE. Ajoutez-en d’autres pour les colonnes que vous utilisez pour filtrer, joindre et trier.
CREATE INDEX idx_products_price ON products (price_cents);
Un index composite couvre plusieurs colonnes et suit la règle du préfixe le plus à gauche : un index sur (category_id, price_cents) aide les requêtes qui filtrent sur category_id, ou sur category_id et price_cents, mais pas sur price_cents seul. Ordonnez les colonnes de la plus sélective et la plus fréquemment filtrée vers les moins prioritaires.
Demandez à l’optimiseur ce qu’il a l’intention de faire :
EXPLAIN
SELECT id, name, price_cents
FROM products
WHERE price_cents < 2500
ORDER BY price_cents
LIMIT 25;
Lisez d’abord la colonne type : const, eq_ref et ref sont de bons signes ; range est acceptable ; index et ALL indiquent un scan. La colonne key indique quel index a été choisi, et rows estime le nombre de lignes qui seront examinées. Un rows élevé pour un petit résultat signifie généralement qu’un index est manquant ou inutilisable. Utilisez EXPLAIN ANALYZE dans MySQL 8 pour exécuter la requête et voir les temps d’exécution réels.
Les index de couverture (covering indexes) méritent d’être mentionnés. Si un index contient toutes les colonnes dont une requête a besoin, MySQL peut répondre en utilisant uniquement l’index sans jamais toucher à la ligne. Ajouter une colonne à un index dans le seul but de le rendre “couvrant” apporte souvent un gain de performance considérable.
utf8mb4 et le piège du charset
Les jeux de caractères sont l’un des points où MySQL surprend souvent les développeurs. Pendant la majeure partie de son historique, le utf8 par défaut était limité à un maximum de trois octets par caractère, ce qui couvre la plupart des textes, mais pas les points de code sur quatre octets comme les emojis et certains scripts rares. Tenter de stocker un emoji dans une colonne utf8 provoque une erreur ou tronque la valeur, selon le mode du serveur.
La solution est utf8mb4, qui est le véritable UTF-8 et permet de tout stocker. Configurez-le à tous les niveaux : serveur, base de données, table et connexion :
CREATE DATABASE shop
CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci;
La collation (ou collation) détermine la manière dont les chaînes sont comparées et triées. utf8mb4_0900_ai_ci est insensible aux accents et à la casse, ce qui correspond généralement aux attentes des utilisateurs pour la recherche. Les collations affectent également le comportement des index ; veillez donc à ce qu’elles soient cohérentes entre les colonnes jointes, car une divergence force des conversions qui peuvent rendre un index inutilisable.
Transactions et niveaux d’isolation
InnoDB vous permet d’utiliser des transactions. Regroupez les écritures liées afin qu’elles réussissent ou échouent ensemble.
START TRANSACTION;
UPDATE inventory SET quantity = quantity - 1
WHERE product_id = 42 AND quantity >= 1;
INSERT INTO orders (product_id, quantity)
VALUES (42, 1);
COMMIT;
Vérifiez le nombre de lignes affectées et ROLLBACK si la mise à jour protégée n’a rien modifié. Le niveau d’isolation par défaut est Repeatable Read, qui fournit un instantané cohérent pour la transaction et, dans InnoDB, utilise des gap locks pour empêcher l’apparition de lignes fantômes. Les autres niveaux sont Read Uncommitted, Read Committed et Serializable.
Une différence utile avec Postgres : en raison du gap locking sous Repeatable Read, les insertions concurrentes dans une plage peuvent provoquer des blocages ou des deadlocks plus facilement. Gardez vos transactions courtes, mettez à jour les lignes dans un ordre cohérent et soyez prêt à relancer une opération en cas de deadlock, qu’InnoDB signale comme une erreur plutôt que de corrompre l’état des données.
Réplication et mise à l’échelle des lectures
La plupart des déploiements MySQL mettent à l’échelle les lectures avant les écritures. Le primaire enregistre chaque modification dans son binary log, et un ou plusieurs réplicas s’y connectent pour rejouer ce log. La réplication est asynchrone par défaut, ce qui signifie qu’un réplica peut accuser un retard de quelques millisecondes ou plus par rapport au primaire.
Le modèle standard consiste à envoyer les écritures vers le primaire et à répartir les lectures entre les réplicas, en acceptant qu’une lecture puisse brièvement retourner des données légèrement obsolètes. Pour garantir la cohérence de lecture après écriture (read-after-write consistency), redirigez les lectures d’un utilisateur vers le primaire pendant un court intervalle après son écriture, ou utilisez les réplicas uniquement pour les données qui tolèrent un certain délai.
La réplication assure également la haute disponibilité. Si le primaire tombe en panne, un réplica peut être promu. Des outils tels que des orchestrateurs ou des services managés automatisent ce basculement (failover), mais il est essentiel de comprendre le compromis entre les modes synchrone et asynchrone : le mode synchrone attend la confirmation des réplicas et risque d’impacter la disponibilité, tandis que le mode asynchrone risque la perte des dernières transactions lors d’un basculement.
Les Upserts avec ON DUPLICATE KEY UPDATE
L’upsert idiomatique de MySQL est INSERT ... ON DUPLICATE KEY UPDATE. Lorsque l’insertion violerait une clé primaire ou une clé unique, c’est la clause de mise à jour qui s’exécute à la place.
INSERT INTO products (sku, name, price_cents, stock, attributes)
VALUES ('SKU-1', 'Widget', 1099, 5, JSON_OBJECT('colour', 'blue'))
ON DUPLICATE KEY UPDATE
price_cents = VALUES(price_cents),
stock = stock + VALUES(stock);
Cette opération est atomique, ce qui est crucial pour les compteurs et la gestion des stocks. L’alternative — effectuer un select, puis décider s’il faut insérer ou mettre à jour — présente une fenêtre de concurrence (race condition) où deux sessions pourraient ne voir aucune ligne et tenter d’insérer toutes les deux. Utilisez l’upsert dès que l’opération est véritablement un “insérer ou mettre à jour”.
Colonnes JSON
Le type JSON de MySQL stocke un document validé et propose des fonctions pour lire et écrire certaines de ses parties.
SELECT id, name,
attributes->>'$.colour' AS colour
FROM products
WHERE attributes->>'$.colour' = 'blue';
Vous pouvez indexer du JSON à l’aide de colonnes générées. MySQL ne peut pas indexer directement une expression JSON ; vous devez donc l’extraire dans une colonne générée stockée, puis indexer celle-ci :
ALTER TABLE products
ADD COLUMN colour VARCHAR(32)
GENERATED ALWAYS AS (attributes->>'$.colour') STORED,
ADD INDEX idx_products_colour (colour);
Utilisez le JSON pour les attributs variables et les payloads de tiers. Conservez les champs que vous filtrez systématiquement sous forme de colonnes réelles ; l’astuce des colonnes générées fonctionne, mais une colonne native avec un type réel est plus simple et plus rapide.
Procédures stockées : à utiliser avec parcimonie
MySQL prend en charge les procédures stockées, les fonctions, les triggers et les événements planifiés. Ils peuvent réduire les allers-retours réseau et centraliser la logique, mais ils déplacent également la logique métier vers un langage plus difficile à tester, à versionner et à déboguer que le code de votre application.
Utilisez-les pour des tâches véritablement liées à la base de données : maintenance massive, migrations de données et jobs qui doivent s’exécuter au plus près des données. Évitez de placer des règles métier fondamentales dans des procédures que seule une équipe comprend. Les triggers sont particulièrement faciles à oublier ; un UPDATE qui déclenche silencieusement trois triggers est difficile à analyser, et les effets de bord cachés finissent toujours par surprendre tout le monde.
Utilisateurs, privilèges et sauvegardes
Créez un utilisateur dédié pour l’application avec uniquement les privilèges nécessaires, plutôt que de vous connecter en tant que root.
CREATE USER 'app'@'%' IDENTIFIED BY 'a-strong-password';
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'app'@'%';
FLUSH PRIVILEGES;
Gardez le DDL et les migrations sur un compte séparé et plus privilégié. Restreignez les hôtes dans la mesure du possible, exigez le TLS et faites tourner vos identifiants.
Pour les sauvegardes, mysqldump produit un dump logique facile à déplacer et à restaurer :
mysqldump --single-transaction --routines --triggers shop > shop.sql
mysql shop_restore < shop.sql
Le flag --single-transaction prend un instantané cohérent des tables InnoDB sans les verrouiller pendant tout le dump. Pour les bases de données volumineuses où le temps de dump est prohibitif, utilisez un outil physique tel que Percona XtraBackup. Dans tous les cas, enregistrez la position du binary log pour pouvoir effectuer une récupération à un instant T (point-in-time recovery), et testez vos restaurations.
MySQL vs MariaDB
MariaDB a débuté comme un fork communautaire de MySQL après le rachat par Oracle et a divergé depuis. Il conserve la majeure partie de la syntaxe MySQL tout en ajoutant ses propres moteurs de stockage et fonctionnalités. De son côté, MySQL a évolué rapidement depuis la version 8 avec un nouveau dictionnaire de données, des fonctions de fenêtrage (window functions) et un support amélioré du JSON.
Pour la plupart des applications, les différences sont minimes. Choisissez MySQL si votre fournisseur managé ou votre contrat de support repose dessus, et MariaDB si vous préférez un projet régi par la communauté ou si vous avez besoin de l’un de ses moteurs. Les concepts relationnels, le SQL et les modèles opérationnels sont transférables de l’un à l’autre, ce qui fait que ce choix est rarement irréversible.
Recherche plein texte dans InnoDB
InnoDB inclut un index plein texte (full-text index), vous n’avez donc pas besoin d’un service séparé pour implémenter une fonctionnalité de recherche. Créez un index FULLTEXT sur les colonnes de texte et interrogez-le avec MATCH ... AGAINST.
CREATE FULLTEXT INDEX ft_products_search
ON products (name, attributes);
SELECT id, name,
MATCH(name, attributes) AGAINST ('running shoe' IN NATURAL LANGUAGE MODE) AS score
FROM products
WHERE MATCH(name, attributes) AGAINST ('running shoe' IN NATURAL LANGUAGE MODE)
ORDER BY score DESC
LIMIT 20;
NATURAL LANGUAGE MODE classe les résultats par pertinence et ignore les mots qui apparaissent dans la majorité des lignes. BOOLEAN MODE vous offre des opérateurs tels que +must, -exclude et "exact phrase" pour un contrôle plus précis. L’index possède une longueur minimale de jeton (token) contrôlée par innodb_ft_min_token_size, ce qui signifie que les mots très courts peuvent être ignorés.
La recherche plein texte est idéale pour les catalogues de produits et la recherche d’articles. Passez à un moteur dédié lorsque vous avez besoin de la tolérance aux fautes de frappe, du facettage ou d’analyses multilingues que MySQL ne propose pas.
Vues et colonnes générées
Une vue est une requête nommée qui se comporte comme une table. Elle est utile pour encapsuler une structure de données commune et pour limiter les colonnes auxquelles un utilisateur de reporting a accès.
CREATE VIEW in_stock AS
SELECT id, sku, name, price_cents, stock
FROM products
WHERE stock > 0;
SELECT * FROM in_stock WHERE price_cents < 2500;
MySQL 8 prenant en charge les fonctions de fenêtrage (window functions), les vues peuvent également pré-formater des résultats analytiques. Gardez à l’esprit qu’une vue exécute sa requête sous-jacente à chaque appel, à moins d’être matérialisée manuellement dans une table ; MySQL ne possédant pas de vues matérialisées natives, les équipes utilisent soit une table de synthèse rafraîchie selon un planning, soit un événement.
Les colonnes générées, présentées dans la section JSON, sont le complément de ce concept : une colonne générée stockée (stored) est calculée lors de l’écriture et peut être indexée, tandis qu’une colonne virtuelle est calculée lors de la lecture. Utilisez une colonne stockée lorsque vous devez indexer l’expression, et une colonne virtuelle lorsque vous n’avez besoin de la valeur qu’occasionnellement.
Modifier les schémas d’une base de données en production
ALTER TABLE sur une table InnoDB volumineuse peut entraîner la reconstruction de la table et maintenir des verrous pendant une période prolongée. MySQL 8 prend en charge le DDL en ligne pour de nombreuses opérations via ALGORITHM=INPLACE, ce qui permet de reconstruire la table sans bloquer les lectures et écritures concurrentes dans bien des cas.
ALTER TABLE products
ADD COLUMN updated_at TIMESTAMP NULL,
ALGORITHM=INPLACE, LOCK=NONE;
ALTER TABLE products
ADD INDEX idx_products_updated (updated_at),
ALGORITHM=INPLACE, LOCK=NONE;
Toutes les modifications ne sont pas effectuées en ligne. Changer le type d’une colonne, ajouter un index FULLTEXT ou reconstruire une clé primaire nécessite souvent encore la copie de la table. Pour ces cas précis, utilisez un outil tel que pt-online-schema-change ou gh-ost, qui créent une table miroir, copient les lignes par lots et effectuent l’échange avec un blocage minimal. Quelle que soit la méthode choisie, passez vos modifications de schéma par des migrations versionnées et testez-les d’abord sur une copie des données de production.
Identifier les requêtes lentes
MySQL enregistre les requêtes qui dépassent long_query_time dans le slow query log, et EXPLAIN permet d’analyser l’exécution d’une requête spécifique. Ensemble, ils constituent le moyen le plus rapide de passer du constat « l’application est lente » à un correctif concret.
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 0.2;
SHOW VARIABLES LIKE 'slow_query_log_file';
Le performance_schema et le schéma sys résument les mêmes données. Commencez par les instructions qui consomment le plus de temps au total, et non par la requête unique la plus lente, puis vérifiez EXPLAIN pour détecter les full scans sur les grandes tables. SHOW PROFILE et EXPLAIN ANALYZE dans MySQL 8 ajoutent des timings par étape lorsque vous avez besoin d’analyser plus en détail.
Charger les données rapidement
Une boucle INSERT ligne par ligne est la méthode la plus lente pour charger des données. Regroupez plusieurs lignes dans une seule instruction, ce qui réduit les allers-retours et permet à InnoDB d’écrire les pages efficacement.
INSERT INTO products (sku, name, price_cents, stock, attributes)
VALUES
('SKU-10', 'Widget', 999, 5, JSON_OBJECT('colour', 'blue')),
('SKU-11', 'Gadget', 1499, 3, JSON_OBJECT('colour', 'red')),
('SKU-12', 'Gizmo', 2499, 7, JSON_OBJECT('colour', 'green'));
Pour les imports massifs, LOAD DATA INFILE diffuse un fichier directement dans une table et s’avère radicalement plus rapide que n’importe quelle forme de INSERT.
LOAD DATA LOCAL INFILE '/data/products.csv'
INTO TABLE products
FIELDS TERMINATED BY ',' ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS
(sku, name, price_cents, stock, @attributes)
SET attributes = CAST(@attributes AS JSON);
Enveloppez les chargements volumineux dans une transaction pour éviter qu’un échec ne laisse une table partiellement importée. Envisagez également de désactiver les index secondaires ou les vérifications d’unicité lors d’un import massif ponctuel, puis de les reconstruire. Pour les écritures quotidiennes de votre application, un INSERT par lots est le choix par défaut recommandé.
Bonnes pratiques
- Utilisez InnoDB pour chaque table et déclarez-le explicitement.
- Créez vos bases de données, tables et connexions avec
utf8mb4et une collation cohérente. - Stockez l’argent sous forme d’unités mineures en entier ou en
DECIMAL, jamais enFLOATouDOUBLE. - Ajoutez des index pour les requêtes que vous exécutez réellement, et confirmez-les avec
EXPLAIN. - Conservez le mode
ONLY_FULL_GROUP_BYpar défaut et écrivez des requêtesGROUP BYcorrectes. - Utilisez
INSERT ... ON DUPLICATE KEY UPDATEpour les upserts atomiques au lieu du schéma « vérifier puis agir » (check-then-act). - Gardez les transactions courtes, mettez à jour les lignes dans un ordre stable et relancez l’opération en cas de deadlock.
- Attribuez à l’application un utilisateur avec le moindre privilège et réservez le DDL à un compte de migration.
- Effectuez des sauvegardes cohérentes avec
--single-transaction, enregistrez la position du binlog et testez les restaurations. - Privilégiez la pagination par keyset aux scans
LIMIT ... OFFSETvolumineux.
Erreurs courantes
- Laisser les tables en MyISAM et perdre ainsi les transactions et le verrouillage au niveau des lignes.
- Utiliser l’ancien
utf8sur trois octets et s’étonner que les emojis ne s’enregistrent pas. - Stocker de l’argent dans des
FLOATet accumuler des erreurs d’arrondi. - Exécuter
UPDATEouDELETEsansWHEREet devoir réécrire l’intégralité de la table. - Indexer chaque colonne au lieu de se concentrer sur les requêtes critiques, ce qui ralentit les écritures.
- Compter sur le fait que
AUTO_INCREMENTsoit sans interruption ou significatif. - Effectuer un cycle lecture-modification-écriture dans le code de l’application au lieu d’un upsert atomique.
- Lire depuis une réplique immédiatement après une écriture et obtenir des données obsolètes.
- Désactiver
ONLY_FULL_GROUP_BYpour masquer une agrégation incorrecte. - Laisser l’application se connecter en tant que
root.
Et après ?
MySQL et ses concepts sont largement répandus. Si vous souhaitez comparer avec l’autre moteur relationnel majeur, consultez le guide PostgreSQL, qui aborde les mêmes concepts avec des configurations par défaut différentes. Pour le langage commun aux deux, le guide SQL détaille les jointures, les CTE et les fonctions de fenêtrage (window functions) en partant des principes fondamentaux. Enfin, comme la plupart des déploiements MySQL s’appuient sur un cache, Redis et le modèle documentaire de MongoDB en sont des compléments naturels.