Stocker du JSON dans un BLOB MySQL fonctionne, mais le verdict est net : des données que le serveur doit lire, filtrer ou indexer vont dans une colonne de type JSON, et le BLOB ne garde du sens que pour des data compressées, chiffrées ou jamais interrogées en SQL. Le JSON data type valide chaque écriture ; le BLOB, non.
- BLOB
- des octets sans jeu de caractères : MySQL ne sait pas qu'il contient du JSON, et un NOT NULL reste le seul contrôle possible.
- Type JSON sous MySQL
- un format binaire validé à l'écriture, où une clé comme
$.privacyse lit sans relire tout le document. - Type JSON sous MariaDB
- un LONGTEXT surveillé par JSON_VALID : la validation existe, le stockage reste du texte.
Ce que MySQL voit dans un BLOB, et dans une colonne JSON
Un BLOB MySQL stocke des données binaires brutes, sans jeu de caractères ni interclassement. Le JSON est pourtant du texte UTF-8 : le ranger dans un blob field oblige à le convertir à chaque lecture (CONVERT(data USING utf8mb4)), et rien n'empêche d'y écrire un document tronqué par un script interrompu. Les outils comme MySQL Workbench ou phpMyAdmin affichent d'ailleurs ce champ comme un contenu binaire, illisible sans manipulation. Pour la base de données, ces données restent opaques : impossible d'en vérifier la structure ou d'y chercher une valeur sans tout relire.
La colonne de type JSON change la nature du stockage. Le support de ce type arrive avec MySQL 5.7.8, et le Reference Manual de MySQL 8.4 lui reconnaît trois apports : un document invalide est refusé à l'écriture, le serveur le convertit dans un format interne qui permet d'atteindre une clé ou un indice de tableau JSON sans analyser tout le texte, et certaines modifications se font en place. Le jeu de caractères est imposé (utf8mb4, interclassement utf8mb4_bin) : la question de l'encodage disparaît. La taille d'un document reste plafonnée par le réglage max_allowed_packet, comme pour un LONGBLOB.
Le type JSON fait partie de MySQL Community Edition, l'édition gratuite installée par la plupart des hébergeurs, comme de l'édition Enterprise : aucune option payante n'est nécessaire pour l'utilisation des fonctions JSON.
BLOB, LONGTEXT ou type JSON, critère par critère
Un BLOB, un LONGTEXT et une colonne JSON acceptent tous trois le même texte, mais MySQL ne les traite pas de la même façon dès qu'il faut vérifier, lire ou indexer le contenu. Le tableau compare les critères qui décident vraiment.
| Critère | BLOB | TEXT ou LONGTEXT | JSON (MySQL) |
|---|---|---|---|
| Encodage connu | non, octets bruts | oui | oui, utf8mb4 imposé |
| Document invalide refusé | non | non, sauf contrainte CHECK ajoutée | oui, à chaque écriture |
| Lecture d'une clé | conversion puis analyse complète | analyse complète du texte | accès direct dans le format binaire |
| Index sur une valeur | par colonne générée, avec conversion | par colonne générée | colonne générée, index multi-valeurs |
| Mise à jour partielle | réécriture du document | réécriture du document | en place avec JSON_SET, JSON_REPLACE, JSON_REMOVE |
| Contenu compressé ou chiffré | adapté | inadapté | impossible, ce n'est plus du JSON |
Lire, chercher et modifier une clé du document
Les fonctions JSON de MySQL travaillent sur un chemin (json path) de la forme $.cle ou $.tableau[0]. Prenons une mysql table de comptes dont la colonne data garde les préférences de chaque utilisateur :
CREATE TABLE comptes (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
data JSON NOT NULL
);
INSERT INTO comptes (data) VALUES
('{"langue":"fr","privacy":"amis","about":"Développeur PHP","abonnement":{"term":"annuel"},"tags":["php","sql"]}');
La lecture d'une clé passe par JSON_EXTRACT ou par l'opérateur abrégé ->>, qui retire en plus les guillemets de la json value :
SELECT id, data->>'$.langue' AS langue
FROM comptes
WHERE data->>'$.privacy' = 'public';
JSON_SEARCH renvoie le chemin où apparaît une chaîne (JSON_SEARCH(data, 'one', 'sql') rend "$.tags[1]"), JSON_CONTAINS teste la présence d'une valeur, et JSON_VALUE, ajoutée en 8.0.21, extrait une valeur en la convertissant dans le type demandé. Pour écrire, JSON_SET modifie une seule clé sans renvoyer tout le document depuis PHP, JSON_MERGE_PATCH fusionne un objet JSON partiel dans l'existant, et JSON_ARRAY ou JSON_OBJECT construisent un tableau ou un objet propre plutôt qu'une chaîne concaténée à la main. Sur un champ BLOB, aucune de ces fonctions ne fait gagner de temps : chaque appel analyse le texte en entier. Dans un script PHP, l'utilisation de ces fonctions évite aussi de rapatrier toutes les données d'une ligne pour n'en modifier qu'une clé : la requête agit directement dans la base, sur la valeur visée.
Indexer une valeur JSON avec une colonne générée
Une colonne JSON ne s'indexe pas directement sous MySQL : le serveur refuse un index posé sur le document entier. Le chemin passe par une generated column, une new column calculée à partir d'une clé, puis un index classique sur cette colonne :
ALTER TABLE comptes
ADD COLUMN langue VARCHAR(5) AS (data->>'$.langue') VIRTUAL,
ADD INDEX idx_langue (langue);
Une requête qui filtre sur data->>'$.langue' ou sur langue profite alors de l'index, et l'optimiseur sait faire le rapprochement entre l'expression et la colonne générée. La colonne VIRTUAL ne prend aucune place dans la table : seules les données de l'index occupent de l'espace dans la base. Une colonne STORED, à l'inverse, recopie la valeur sur le disque, ce qui sert par exemple quand la même clé alimente un tri fréquent.
Index multi-valeurs pour un tableau JSON
Depuis MySQL 8.0.17, InnoDB accepte un index à valeurs multiples sur un tableau JSON, utile pour une liste d'étiquettes : ADD INDEX idx_tags ((CAST(data->'$.tags' AS CHAR(20) ARRAY))). Une condition MEMBER OF ou JSON_CONTAINS sur ce tableau l'utilise. Le choix du moteur compte ici : ces index n'existent qu'avec InnoDB, ce que rappelle la comparaison entre MyISAM, InnoDB et MEMORY.
MariaDB ne range pas le JSON comme MySQL
MariaDB traite le JSON autrement que MySQL, et l'écart surprend souvent lors d'un changement d'hébergement. Sous MariaDB, JSON n'est pas un type à part : la MariaDB Knowledge Base le décrit comme un alias de LONGTEXT avec l'interclassement utf8mb4_bin, auquel le serveur ajoute d'office une contrainte JSON_VALID. La validation est assurée, mais le stockage reste du texte, sans le format binaire de MySQL. Les fonctions JSON_EXTRACT, JSON_SET ou JSON_CONTAINS existent des deux côtés ; les opérateurs -> et ->> sont propres à MySQL, et une requête qui les emploie échoue sous MariaDB. Une application portable écrit donc JSON_UNQUOTE(JSON_EXTRACT(...)) en toutes lettres. Autre exemple d'écart : les index multi-valeurs n'existent pas sous MariaDB, où l'indexation des données JSON passe uniquement par une colonne générée.
Passer une colonne BLOB au type JSON sans casse
Convertir un champ BLOB en champ JSON tient en une instruction ALTER TABLE, à condition que chaque ligne contienne un document valide : une seule ligne fautive fait échouer l'opération.
- Exporter la table avant toute modification de structure.
- Repérer les lignes invalides :
SELECT id FROM comptes WHERE data IS NOT NULL AND JSON_VALID(CONVERT(data USING utf8mb4)) = 0; - Corriger ou vider ces lignes, puis changer le field type :
ALTER TABLE comptes MODIFY data JSON NULL; - Contrôler côté PHP que
json_decode()reçoit bien une chaîne : selon le pilote, la valeur arrive désormais en texte et non plus en binaire. - Sous MariaDB, vérifier le résultat avec
SHOW CREATE TABLE, qui affiche un LONGTEXT et sa contrainte.
Une colonne dérivée du document peut ensuite être tenue à jour par un trigger MySQL déclenché à chaque écriture, si la colonne générée ne suffit pas.
Les profils pour qui le BLOB reste défendable
Le BLOB reste le bon conteneur pour un document compressé ou chiffré avant stockage : il n'est plus du JSON lisible par le serveur, et le type JSON le refuserait. Même logique pour une application qui relit le contenu en bloc sans jamais l'interroger en SQL, un cache sérialisé par exemple : le gain du type JSON se limite alors à la validation, qui suffit pourtant souvent à justifier la conversion.
À l'inverse, des documents qui portent tous les mêmes clés ne réclament ni BLOB ni JSON : de vraies colonnes typées s'indexent et se contrôlent plus simplement. Un exemple courant : des données de formulaire au schéma variable, où chaque objet porte ses propres clés, profitent du type JSON, alors qu'un catalogue dont chaque produit a les mêmes attributs se range mieux en colonnes. Pour une base reprise d'un autre moteur qui gardait déjà du JSON en texte, la méthode pour convertir des tables SQLite vers MySQL s'applique avant de passer les colonnes concernées au type JSON.
Extraire du JSON d'un BLOB, place occupée et valeur par défaut
Comment extraire du JSON d'un champ BLOB ?
Il faut convertir les octets en texte avant d'appeler une fonction JSON : JSON_EXTRACT(CONVERT(data USING utf8mb4), '$.langue'). La conversion se répète à chaque ligne lue, ce qui rend le filtrage lent sur une grosse table.
Une colonne JSON prend-elle plus de place qu'un BLOB ?
Le manuel de MySQL indique un espace comparable à celui d'un LONGBLOB ou d'un LONGTEXT, auquel s'ajoutent quelques octets de métadonnées du format binaire.
Peut-on donner une valeur par défaut à une colonne JSON ?
Depuis MySQL 8.0.13, une valeur par défaut s'écrit sous forme d'expression entre parenthèses, par exemple DEFAULT (JSON_OBJECT()). Une valeur littérale simple reste refusée.
Sources : MySQL 8.4 Reference Manual, chapitre « The JSON Data Type », colonnes générées et index multi-valeurs ; MariaDB Knowledge Base, type JSON ; manuel de php.net, json_decode().













