name: mysql
description: Expertise approfondie spécifique à MySQL/MariaDB — types de données et charset/collation corrects (utf8mb4, DECIMAL pour l'argent, VARCHAR/INT de taille appropriée, compromis ENUM), InnoDB vs MyISAM, conception d'index et réglage de requêtes piloté par EXPLAIN (ordre des index composites, index couvrants, prédicats sargables, pagination par jeu de clés), isolation des transactions et verrouillage (REPEATABLE READ, verrous d'écart, deadlocks), et pièges de correction spécifiques à MySQL. À utiliser lorsque package.json liste mysql2 ou mysql, le dialecte/fournisseur d'un ORM est défini sur mysql (Prisma provider = "mysql", Sequelize dialect: 'mysql', TypeORM type: 'mysql'/'mariadb'), un service docker-compose exécute une image mysql/mariadb, ou une chaîne de connexion mysql:// est présente, y compris lorsqu'on demande directement (en français ou en anglais) d'écrire ou vérifier un schéma/requête MySQL correct et performant. Ne remplace pas le workflow de migration indépendant du moteur de database-schema-dev ni le code de couche d'accès de backend-dev — c'est l'expertise MySQL spécifique que les deux utilisent lorsque le moteur est effectivement MySQL/MariaDB.
MySQL
Couche d'expertise spécifique à MySQL de database-schema-dev et backend-dev :
ces agents sont indépendants du moteur et correspondent à l'outil de migration/ORM qu'un
projet possède déjà — cette compétence est la connaissance réelle de MySQL qu'ils utilisent
lorsque le moteur se trouve être MySQL, ou son fork proche-compatible MariaDB.
Lorsque MariaDB se comporte différemment, cela est signalé — ne présumez pas d'une parité
complète sur les fonctionnalités avancées.
Étape 1 — Confirmer que le moteur est effectivement MySQL/MariaDB
Vérifiez package.json pour mysql2/mysql, un dialecte/fournisseur d'ORM
(provider = "mysql" dans Prisma, dialect: 'mysql' dans Sequelize,
type: 'mysql'/'mariadb' dans TypeORM), un service docker-compose.yml
exécutant une image mysql/mariadb, ou une chaîne de connexion mysql://. Si
rien de tout cela n'est vérifié, arrêtez-vous — utilisez les valeurs par défaut
indépendantes du moteur de database-schema-dev/backend-dev, ou la compétence
spécifique au moteur correspondant à la place.
Étape 2 — Charset, collation, moteur de stockage
- Le charset par défaut est
utf8mb4, pasutf8— leutf8de MySQL est un alias de 3 octets maximum qui ne peut pas contenir la plupart des emojis/caractères astraux — avec une collation explicite (utf8mb4_0900_ai_cisur MySQL 8+ ; MariaDB n'a pas de collations0900, utilisezutf8mb4_unicode_cipour tout ce qui cible les deux). InnoDBsauf s'il y a une raison spécifique pourMyISAM(pas de transactions, pas de verrous de ligne, pas de clés étrangères) — c'est la valeur par défaut depuis MySQL 5.5/MariaDB 10.x, donc il s'agit généralement simplement de confirmer que rien ne le remplace.- La collation
_cisignifie que'[email protected]'correspond à'[email protected]'— utilisez_bin/_cs(ou une colonne hachée) pour tout ce qui doit comparer de manière sensible à la casse (jetons, secrets).
Étape 3 — Types de données corrects
- Argent/décimales exactes :
DECIMAL(p,s), jamaisFLOAT/DOUBLE— les nombres flottants binaires ne peuvent pas représenter la plupart des fractions en base 10 exactement. - Entiers de taille appropriée (
TINYINT/SMALLINT/INT/BIGINT) en fonction des plages réelles, pasBIGINTpartout par habitude — les types plus petits signifient des index plus petits.UNSIGNEDest une marge de manœuvre gratuite sur une colonne d'identifiant, qui n'est jamais négative. DATETIMEpour une valeur d'horloge sans conversion de fuseau horaire prévue ;TIMESTAMP(normalisé UTC, se convertit automatiquement selon le fuseau horaire de la session, limite de plage 2038) uniquement pour un instant réel dans le temps — ne le définissez pas par défaut.VARCHAR(n)dimensionné selon la contrainte réelle, pasVARCHAR(255)par eréflexe ;TEXT/BLOBpour un contenu véritablement illimité — aucun des deux ne prend de valeur par défaut ou d'index complet (non préfixé).ENUMuniquement pour un ensemble fixe et rarement modifié — les valeurs se trient par ordre de définition, pas alphabétiquement (une surprise dansORDER BY), et ajouter une valeur est uniquement métadonnée sur MySQL 8 mais peut réécrire la table sur MariaDB/MySQL plus ancien. Une table de référence évolue mieux lorsque l'ensemble s'agrandit ou a besoin de métadonnées.JSONnatif (MySQL 5.7.8+/MariaDB 10.2+ — leJSONde MariaDB est en faitLONGTEXTplus uneCHECK, pas un type binaire, donc aucun des avantages de stockage binaire/mise à jour partielle de MySQL ne s'applique) uniquement pour des données véritablement sans schéma — tout ce qui est régulièrement filtré ou joint appartient à une colonne réelle.
Étape 4 — Conception d'index
- Index composites : colonne(s) filtrée(s) par égalité d'abord, la colonne de
tri/plage en dernier —
(status, created_at)sertWHERE status = ? ORDER BY created_at; l'ordre inverse ne le fait pas. - Règle du préfixe le plus à gauche : un index sur
(a, b, c)sert également les recherches(a)et(a, b)— n'ajoutez pas un index(a)redondant à côté. - Un index couvrant (chaque colonne sélectionnée incluse) permet à MySQL de
répondre à partir de l'index seul (
Using indexdansEXPLAIN) — utile pour une requête chaude et étroite, pas par défaut sur chaque requête. - Index de préfixe (
col(20)) pour lesVARCHAR/TEXTlongs — vérifiezCOUNT(DISTINCT LEFT(col, n))par rapport àCOUNT(DISTINCT col)pour dimensionnern. - Pagination profonde :
LIMIT 20 OFFSET 100000scanne et jette encore 100 000 lignes à chaque page. La pagination par jeu de clés (WHERE (created_at, id) < (:last_created, :last_id) ORDER BY created_at DESC, id DESC LIMIT 20) réutilise le même index composite et va directement au curseur — utilisez-la pour le défilement infini ou la pagination API profonde à la place deOFFSET. - Chaque index coûte du temps d'écriture et de l'espace disque — ajoutez des index pour des modèles de requêtes réels (database-schema-dev Étape 1), pas de manière spéculative par colonne.
Étape 5 — Réglage des requêtes piloté par EXPLAIN
- Exécutez
EXPLAIN(ouEXPLAIN ANALYZE, MySQL 8.0.18+/MariaDB 10.4+) avant de croire qu'une requête est rapide — ne jugez pas uniquement par la forme de la requête. type: ALLest un scan de table complet ;ref/range/constsignifient qu'un index est réellement utilisé — une requête valant la peine d'être optimisée ne devrait pas montrerALLau-delà d'une taille de table triviale.Extra: Using filesort/Using temporarysignifie que MySQL a trié/matérialisé en dehors d'un index — généralement corrigé par un index correspondant auORDER BY/GROUP BY.- Une fonction d'encapsulation ou une conversion implicite sur une colonne indexée
(
WHERE YEAR(created_at) = ..., comparer une colonne de chaîne à un nombre) rend l'index inutilisable — réécrivez comme une plage de colonne nue à la place (created_at >= '2024-01-01' AND created_at < '2025-01-01'). SELECT *sur une table large rend les index couvrants inutiles et extrait des colonnesTEXT/BLOBque personne n'a demandées — sélectionnez uniquement ce qui est utilisé.
Étape 6 — Transactions et verrouillage
- InnoDB verrouille les lignes, pas les tables, mais l'isolation par défaut
REPEATABLE READprend également des verrous d'écart sur les scans de plage dansUPDATE/DELETE/SELECTverrouillant — une condition de plage peut bloquer plus que les lignes correspondantes.READ COMMITTEDévite les verrous d'écart si les lectures répétables ne sont pas nécessaires. - Cet instantané ne couvre qu'un
SELECTsimple — une lecture verrouillante dans la même transaction (SELECT ... FOR UPDATE,UPDATE,DELETE) lit les données validées les plus récentes à la place, elle peut donc voir une ligne que l'instantané n'a jamais montrée. Ne présumez pas que l'isolation d'instantané protège également une lecture verrouillante. - Gardez les transactions courtes — une transaction ouverte conserve ses verrous et,
sous
REPEATABLE READ, bloque également la purge par InnoDB des anciennes versions de lignes. SELECT ... FOR UPDATEpour verrouiller les lignes lues puis écrites dans une seule transaction (vérification du solde avant débit) ; acquérez les verrous dans un ordre de lignes cohérent dans toute la base de code pour éviter les deadlocks.- Un deadlock (
ERROR 1213, SQLSTATE40001) est attendu sous une contention réelle — InnoDB annule automatiquement un côté ; attrapez ce code et réessayez au lieu de le "réparer" avec une transaction plus longue.SHOW ENGINE INNODB STATUS→LATEST DETECTED DEADLOCKmontre ce que chaque côté détenait/attendait. - Autocommit est activé par connexion par défaut — l'atomicité multi-déclarations nécessite une transaction explicite (ou l'encapsulation de l'ORM), pas seulement une adjacency dans le code.
Étape 7 — Pièges spécifiques à MySQL et divergence MariaDB
sql_modedevrait inclureSTRICT_TRANS_TABLES(valeur par défaut depuis MySQL 5.7/MariaDB 10.2) — sans cela, les valeurs hors plage/de mauvais type sont silencieusement tronquées au lieu de générer une erreur ; confirmez qu'il n'a pas été assoupli.- Les valeurs
AUTO_INCREMENTne sont pas sans écart (une annulation en consomme toujours une) — ne comptez jamais sur des identifiants contigus. - Le
time_zonede session affecte la conversionTIMESTAMPmais pasDATETIME— un pool avec des fuseaux horaires mixtes par connexion est un véritable bug "mauvaise heure" ; épinglez-le explicitement. - MariaDB a ses propres extensions sans équivalent MySQL (
CREATE SEQUENCE,RETURNINGsur les écritures) — ne les utilisez pas si MySQL est également une cible ; les fonctions de fenêtre/CTE nécessitent MySQL 8.0+/MariaDB 10.2+, vérifiez d'abord la version épinglée. - L'optimiseur et la sortie
EXPLAINde MariaDB ne sont pas le même moteur que ceux de MySQL — réexécutezEXPLAINsur le moteur réellement utilisé plutôt que de vous fier à un plan réglé sur l'autre.
Étape 8 — Auto-vérification avant de signaler comme terminé
Confirmez que chaque colonne nouvelle/modifiée a un type intentionnel et que le
charset par défaut est utf8mb4. Confirmez que tout nouvel index est soutenu par
un modèle de requête réel avec une sortie EXPLAIN montrant qu'il est utilisé, et
que tout point de terminaison de liste profonde/illimitée utilise keyset au lieu de
OFFSET. Confirmez que les écritures multi-déclarations sont encapsulées dans une
transaction explicite et que le comportement d'isolation d'une lecture verrouillante
est délibéré, pas supposé. Si le fichier compose/CI épingle MariaDB, confirmez
qu'aucun ajout ne suppose une fonctionnalité exclusive à MySQL-8.
Ce qu'il ne faut pas faire
- N'utilisez pas le charset
utf8niFLOAT/DOUBLEpour l'argent —utf8mb4etDECIMALsont les valeurs par défaut correctes. - N'ajoutez pas d'index sans modèle de requête derrière, ou ne qualifiez pas une
requête de "rapide" sans vérifier
EXPLAIN. - N'utilisez pas la pagination
OFFSETsur une liste qui peut paginer profondément ou s'exécuter sans limite — utilisez keyset. - Ne laissez pas une transaction ouverte plus longtemps que le travail qu'elle
protège, surtout sous les verrous d'écart de
REPEATABLE READ. - Ne comptez pas sur la contiguïté de
AUTO_INCREMENTou la conversion de type implicite pour l'exactitude. - Ne présumez pas qu'une cible MariaDB possède des fonctionnalités exclusives à
MySQL-8 (JSON natif,
utf8mb4_0900_ai_ci) sans vérifier la version épinglée. - N'écrivez pas vous-même le code de migration/couche d'accès indépendant du
moteur — c'est le rôle de
database-schema-dev/backend-dev; cette compétence est la connaissance de MySQL qu'ils appliquent par-dessus.