JSON_STORAGE_FREE

La función JSON_STORAGE_FREE devuelve el número de bytes de espacio libre dentro de un documento JSON almacenado en una columna, después de haber sido actualizado parcialmente. A partir de MySQL 8.0, el motor InnoDB puede realizar actualizaciones parciales de documentos JSON, modificando valores individuales sin reescribir todo el documento. Cuando el nuevo valor es más pequeño que el anterior, se crea espacio libre que JSON_STORAGE_FREE permite medir.

Sintaxis

JSON_STORAGE_FREE(documento_json)

La función recibe una expresión JSON y devuelve un entero que representa los bytes de espacio libre dentro del documento. Si el documento no ha sido actualizado parcialmente, o si el argumento es un literal JSON (no una columna almacenada), la función devuelve 0.

Comportamiento básico

Para entender cómo funciona, hay que comprender primero las actualizaciones parciales de JSON en MySQL. Cuando usas JSON_SET, JSON_REPLACE o JSON_REMOVE para modificar un documento almacenado en una columna, MySQL puede actualizar solo los bytes afectados en lugar de reescribir todo el documento. Esto es mucho más eficiente, especialmente para documentos grandes.

Sin embargo, si el nuevo valor es más corto que el anterior, queda un hueco de bytes sin usar dentro del documento. JSON_STORAGE_FREE mide exactamente ese hueco.

Veamos un ejemplo práctico:

CREATE TABLE configuraciones (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(100),
    config JSON
);
 
INSERT INTO configuraciones (nombre, config) VALUES
('servidor_web', '{"host": "www.tiendaonline.com", "puerto": 8080, "protocolo": "HTTPS", "timeout": 30000, "max_conexiones": 500}');

Primero, verifiquemos el tamaño y espacio libre antes de cualquier actualización:

SELECT
    nombre,
    JSON_STORAGE_SIZE(config) AS tamanio_total,
    JSON_STORAGE_FREE(config) AS espacio_libre
FROM configuraciones
WHERE nombre = 'servidor_web';
nombretamanio_totalespacio_libre
servidor_web1190

El espacio libre es 0 porque el documento no ha sido actualizado parcialmente. Ahora modifiquemos un valor por otro más corto:

UPDATE configuraciones
SET config = JSON_SET(config, '$.host', 'web.local')
WHERE nombre = 'servidor_web';

Hemos reemplazado "www.tiendaonline.com" (20 caracteres) por "web.local" (9 caracteres). Comprobemos el resultado:

SELECT
    nombre,
    JSON_STORAGE_SIZE(config) AS tamanio_total,
    JSON_STORAGE_FREE(config) AS espacio_libre
FROM configuraciones
WHERE nombre = 'servidor_web';
nombretamanio_totalespacio_libre
servidor_web11911

El tamaño total sigue siendo 119 bytes (no se ha reducido), pero ahora hay 11 bytes de espacio libre dentro del documento. MySQL no compacta automáticamente el documento: mantiene el espacio libre para posibles actualizaciones futuras que podrían reutilizarlo.

Cuándo se crea espacio libre

El espacio libre aparece en situaciones específicas:

Cuando reemplazas un valor por uno más corto con JSON_SET o JSON_REPLACE:

UPDATE configuraciones
SET config = JSON_REPLACE(config, '$.protocolo', 'HTTP')
WHERE nombre = 'servidor_web';

Pasamos de "HTTPS" (5 caracteres) a "HTTP" (4 caracteres), lo que libera un poco más de espacio.

Cuando eliminas una clave con JSON_REMOVE:

UPDATE configuraciones
SET config = JSON_REMOVE(config, '$.max_conexiones')
WHERE nombre = 'servidor_web';

Al eliminar la clave max_conexiones y su valor, todo ese espacio se convierte en espacio libre dentro del documento.

SELECT
    JSON_STORAGE_SIZE(config) AS tamanio_total,
    JSON_STORAGE_FREE(config) AS espacio_libre,
    JSON_STORAGE_SIZE(config) - JSON_STORAGE_FREE(config) AS tamanio_efectivo
FROM configuraciones
WHERE nombre = 'servidor_web';
tamanio_totalespacio_libretamanio_efectivo
1193485

El documento sigue ocupando 119 bytes en disco, pero 34 de ellos son espacio libre. Los datos útiles solo necesitan 85 bytes.

Caso práctico: monitorizar fragmentación

En una tabla con actualizaciones frecuentes de columnas JSON, la fragmentación puede acumularse. Puedes monitorizar el nivel de fragmentación de toda la tabla:

CREATE TABLE productos (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(200),
    precio DECIMAL(10,2),
    metadata JSON
);
 
INSERT INTO productos (nombre, precio, metadata) VALUES
('iPhone 15 Pro', 1299.99, '{"descripcion_larga": "El iPhone más avanzado con chip A17 Pro, cámara de 48MP y pantalla Super Retina XDR de 6.1 pulgadas", "palabras_clave": "smartphone,apple,ios,5g,premium", "ultima_revision": "2025-01-15"}'),
('Samsung Galaxy S24', 899.99, '{"descripcion_larga": "Smartphone Samsung con inteligencia artificial Galaxy AI, cámara de 200MP y pantalla Dynamic AMOLED 2X", "palabras_clave": "smartphone,samsung,android,5g", "ultima_revision": "2025-02-01"}'),
('MacBook Air M3', 1399.00, '{"descripcion_larga": "Portátil ultraligero con chip Apple M3, 18 horas de batería y pantalla Liquid Retina de 15 pulgadas", "palabras_clave": "portatil,apple,macbook,ultrabook", "ultima_revision": "2025-01-20"}');

Ahora simulemos varias actualizaciones que crean fragmentación:

UPDATE productos SET metadata = JSON_SET(metadata, '$.ultima_revision', '2025-06-01');
UPDATE productos SET metadata = JSON_REPLACE(metadata, '$.palabras_clave', 'movil,apple');
SELECT
    nombre,
    JSON_STORAGE_SIZE(metadata) AS total_bytes,
    JSON_STORAGE_FREE(metadata) AS bytes_libres,
    ROUND(JSON_STORAGE_FREE(metadata) / JSON_STORAGE_SIZE(metadata) * 100, 1) AS pct_fragmentado
FROM productos;
nombretotal_bytesbytes_librespct_fragmentado
iPhone 15 Pro2322510.8
Samsung Galaxy S24229187.9
MacBook Air M3224229.8

La columna pct_fragmentado muestra qué porcentaje del espacio almacenado es desperdicio. En tablas con millones de filas y actualizaciones frecuentes, un 10-15% de fragmentación puede representar gigabytes de espacio desperdiciado.

Caso práctico: decidir cuándo compactar

Para tablas grandes, puedes establecer alertas cuando la fragmentación supere un umbral:

SELECT
    COUNT(*) AS filas_fragmentadas,
    SUM(JSON_STORAGE_FREE(metadata)) AS total_bytes_libres,
    ROUND(SUM(JSON_STORAGE_FREE(metadata)) / 1024 / 1024, 2) AS mb_desperdiciados,
    ROUND(AVG(
        JSON_STORAGE_FREE(metadata) / JSON_STORAGE_SIZE(metadata) * 100
    ), 1) AS pct_promedio
FROM productos
WHERE JSON_STORAGE_FREE(metadata) > 0;
filas_fragmentadastotal_bytes_libresmb_desperdiciadospct_promedio
3650.009.6

Con solo tres filas el desperdicio es insignificante, pero en tablas de producción con millones de registros, esta consulta te indica cuándo vale la pena compactar.

Para compactar un documento y eliminar su espacio libre, puedes reasignar la columna a sí misma:

UPDATE productos
SET metadata = metadata
WHERE JSON_STORAGE_FREE(metadata) > 0;

Esta actualización fuerza a MySQL a reescribir completamente cada documento, eliminando el espacio libre:

SELECT
    nombre,
    JSON_STORAGE_SIZE(metadata) AS total_bytes,
    JSON_STORAGE_FREE(metadata) AS bytes_libres
FROM productos;
nombretotal_bytesbytes_libres
iPhone 15 Pro2070
Samsung Galaxy S242110
MacBook Air M32020

Después de la compactación, el tamaño total se ha reducido y el espacio libre es 0 en todas las filas.

Cuándo la función devuelve 0

JSON_STORAGE_FREE devuelve 0 en varios escenarios:

Cuando el documento nunca ha sido actualizado parcialmente (recién insertado):

INSERT INTO productos (nombre, precio, metadata) VALUES
('Cable USB-C', 19.99, '{"tipo": "USB-C"}');
 
SELECT JSON_STORAGE_FREE(metadata)
FROM productos WHERE nombre = 'Cable USB-C';

El resultado es 0 porque el documento no ha sufrido actualizaciones parciales.

Cuando pasas un literal JSON en lugar de una columna:

SELECT JSON_STORAGE_FREE('{"clave": "valor"}') AS resultado;
resultado
0

Los literales nunca tienen espacio libre porque no han sido almacenados ni actualizados.

Cuando una actualización usa un valor más largo que el anterior, no se genera espacio libre (puede requerir reescritura completa del documento).

Manejo de NULL

Si el argumento es NULL, la función devuelve NULL:

SELECT JSON_STORAGE_FREE(NULL) AS resultado;
resultado
NULL

Si la columna JSON de una fila es NULL, el resultado también es NULL, no 0.

Combinación con otras funciones

Una consulta completa de diagnóstico de almacenamiento JSON combina JSON_STORAGE_SIZE, JSON_STORAGE_FREE y otras funciones:

SELECT
    nombre,
    JSON_STORAGE_SIZE(metadata) AS almacenado,
    JSON_STORAGE_FREE(metadata) AS libre,
    JSON_STORAGE_SIZE(metadata) - JSON_STORAGE_FREE(metadata) AS efectivo,
    JSON_DEPTH(metadata) AS profundidad,
    JSON_LENGTH(metadata) AS num_claves
FROM productos
ORDER BY JSON_STORAGE_SIZE(metadata) DESC;

Este tipo de consulta te da una visión completa del estado de tus documentos JSON: cuánto ocupan, cuánto desperdician y qué tan complejos son. Es una herramienta esencial para el mantenimiento de bases de datos que hacen uso intensivo de columnas JSON.

En el siguiente artículo veremos JSON_QUOTE para escapar cadenas como valores JSON.

Escrito por Eduardo Lázaro