Aller au contenu principal
Questions et réponses sur l'hébergement web

BLOB ou colonne JSON native : où ranger un document JSON sous MySQL

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 $.privacy se 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èreBLOBTEXT ou LONGTEXTJSON (MySQL)
Encodage connunon, octets brutsouioui, utf8mb4 imposé
Document invalide refusénonnon, sauf contrainte CHECK ajoutéeoui, à chaque écriture
Lecture d'une cléconversion puis analyse complèteanalyse complète du texteaccès direct dans le format binaire
Index sur une valeurpar colonne générée, avec conversionpar colonne généréecolonne générée, index multi-valeurs
Mise à jour partielleréécriture du documentréécriture du documenten 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.

  1. Exporter la table avant toute modification de structure.
  2. Repérer les lignes invalides : SELECT id FROM comptes WHERE data IS NOT NULL AND JSON_VALID(CONVERT(data USING utf8mb4)) = 0;
  3. Corriger ou vider ces lignes, puis changer le field type : ALTER TABLE comptes MODIFY data JSON NULL;
  4. 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.
  5. 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().

Dernières réponses

Questions proches

▲