Ponés un JSON pesado , una factura completa, el payload de una API como campos adicionales a tu tabla transaccional. Claro eso funciona pero luego de un tiempo , esa tabla es la más lenta de toda tu base de datos.
Para guardarlo bien yo recomiendo separar el JSON en una tabla aparte de la transaccional, y si el volumen es grande ponelo en su propio filegroup. Eso te ayudara mucho a tus backups/Restores y rendimiento del motor
Puntos clave:
- Separá el JSON de la tabla transaccional: una tabla aparte, no una columna más en la tabla principal.
- Validalo siempre con
CHECK (ISJSON(...) = 1): sin eso no hay garantía de que el contenido sea JSON válido. - Usá un filegroup dedicado si el volumen es grande, para poder respaldarlo aparte sin afectar el resto de la base.
- El soporte de funciones JSON existe desde SQL Server 2016 (ISJSON, JSON_VALUE, OPENJSON, FOR JSON); en versiones anteriores no hay soporte nativo.
- OPENJSON sirve para desarmar JSON una sola vez, pero no abuses de el ya que es lento y consume CPU
- No indexes directamente una columna NVARCHAR(MAX) con JSON: si necesitas filtrar por una propiedad, esa propiedad tiene que ser una columna relacional.
- SQL Server 2025 suma un tipo JSON nativo binario, más liviano que NVARCHAR(MAX) para el mismo contenido.
Por qué la tabla se puede volver lenta
SQL Server es un motor row-store. Para leer una fila lee la página de 8 KB entera donde esa fila vive. No existe leer «solo una columna».
Si estas guardando el JSON como NVARCHAR(MAX) en la misma tabla donde están los otros datos , cada fila pesa más y entran menos filas por página. Esos son los nvarchar max performance issues de los que casi nadie te dice: el costo no lo paga la consulta que pide el JSON, lo paga tu tabla completa.
Un SELECT que ni siquiera nombra la columna del JSON. Un UPDATE de un campo chico. El backup. El rebuild de índice. Todo más caro, para siempre.
La solución: sacar el JSON a otra tabla
La respuesta no es dejar de usar JSON. Es separarlo en su propia tabla, con su propio filegroup.
CREATE TABLE dbo.Movimientos (
MovimientoId INT PRIMARY KEY,
CuentaId INT NOT NULL,
FechaMovimiento DATETIME2(0) NOT NULL,
Importe DECIMAL(18,2) NOT NULL,
Estado VARCHAR(20) NOT NULL
);
CREATE TABLE dbo.MovimientosAnexos (
MovimientoId INT PRIMARY KEY REFERENCES dbo.Movimientos(MovimientoId),
JsonEntrada NVARCHAR(MAX) CHECK (ISJSON(JsonEntrada) = 1),
JsonSalida NVARCHAR(MAX) CHECK (ISJSON(JsonSalida) = 1)
) ON FG_JSON;Dos detalles que hacen la diferencia:
El CHECK (ISJSON(...) = 1) sin eso, la columna acepta cualquier texto y no tenés ninguna garantía de que lo guardado sea JSON válido.
El filegroup dedicado (FG_JSON) te habilita backup y restore independiente sin castigar el tiempo de recuperación de tus datos transaccionales críticos, por ejemplo para un ambiente de desarrollo en donde no queres restaurar esos JSON
La tabla transaccional queda mas chica.
Cuánto cuesta realmente: el número
La siguiente consulta es idéntica en cada modelo, ambas hacen un ClusteredIndex Scan y mira lo que pasa
SELECT TOP (50) MovimientoId, CuentaId, FechaMovimiento, Importe, Estado
FROM dbo.Movimientos_ConJson
WHERE CuentaId = 42
ORDER BY FechaMovimiento DESC OPTION (RECOMPILE);
SELECT TOP (50) MovimientoId, CuentaId, FechaMovimiento, Importe, Estado
FROM dbo.Movimientos
WHERE CuentaId = 42
ORDER BY FechaMovimiento DESC OPTION (RECOMPILE);

Sin llegara a usar por alguna razon un Clustered Index Scan las lecturas lógicas y el tiempo de ejecución no se parecen en nada:
| Escenario | Lecturas lógicas | Tiempo de ejecución |
|---|---|---|
| JSON embebido en la tabla | 52716 | 462 ms |
| JSON en tabla separada | 33723 | 129 ms |
Ahora obviamente si haces un index Seek la cosa puede cambiar y se acercan, no son iguales 100%

| Escenario Seek Index | Lecturas lógicas | Tiempo de ejecución |
|---|---|---|
| JSON embebido en la tabla | 7768 | 76 ms |
| JSON en tabla separada | 5811 | 53 ms |
Tamaños de índices
Mira lo que pasa en cada modelo con los índices Clustered
| Tabla | Indice | Páginas usadas | Páginas Reservadas | Tamaño MB |
|---|---|---|---|---|
| Movimientos | CLUSTERED | 33238 | 33258 | 259.67 |
| Movimientos_ConJson | CLUSTERED | 2509335 | 2509351 | 19604.18 |
El tamaño del clustered en tu tabla con el JSON es mas grande, entonces si tenes datos transaccionales + JSON es un problema de rendimiento a la larga
Mirá la demo completa en video 👇
Mis 5 reglas para guardar JSON en SQL Server
- Separá desde el diseño, no cuando duele. Migrar una tabla de 300 GB con JSON adentro en producción es un proyecto; hacerlo el día uno es una decisión de cinco minutos.
- Siempre
CHECK (ISJSON(columna) = 1). Es gratis y te evita descubrir el texto corrupto tres años después. - Filegroup propio para el JSON. Backup, restore y mantenimiento independientes del core transaccional.
- No filtres por dentro del JSON en tus consultas del día a día. Si consultás una propiedad seguido, esa propiedad tiene que ser una columna relacional. Punto.
- Para pasar filas a un stored procedure, usá table-valued parameters, no JSON. Con JSON perdés el tipado y el optimizador no sabe cuántas filas hay ahí adentro.
Si el problema ya no es solo el diseño de esta tabla sino que tenés consultas lentas en general, te puede servir revisar SQL Server Lento: Detectá Índices Faltantes o la guía completa de cómo mejorar la performance de SQL Server.
Mi experiencia en Triggerdb
Con mis clientes me encuentro siempre con la misma escena: alguien metió el JSON de una API o de una factura directo en la tabla principal «porque era más rápido de desarrollar», y ahora esa tabla es el cuello de botella de todo el sistema.
Lo que más me cuesta explicar en esas reuniones es que el problema no está en la consulta lenta que vinieron a mostrarme. Está en el diseño de la tabla. Podés hacer tuning a esa query todo lo que quieras: mientras la fila siga siendo ancha, seguís leyendo páginas de más.
El error más común: creer que hay que leer el JSON para tener problemas
Lo que nadie te dice es que ni siquiera hace falta leer el JSON.
Un UPDATE que solo cambia el Estado de un movimiento sin acercarse a la columna JSON sale más caro en la tabla ancha. SQL Server reescribe la fila entera, no la columna que cambiaste. Más log de transacciones, más páginas sucias, más trabajo para el checkpoint.
Ese es el costo invisible: lo pagás en operaciones que no tienen nada que ver con el JSON.
Dato de color: SQL Server 2025 suma un tipo de dato JSON nativo, binario, que pesa bastante menos que el NVARCHAR(MAX) equivalente. Vale la pena evaluarlo
Preguntas frecuentes
Si esto ya te está pasando en producción, un servicio DBA SQL Server puede auditar el diseño completo y decirte qué separar primero.
¿Cómo guardar un JSON en SQL Server?
En una tabla separada de la transaccional, con CHECK (ISJSON(...) = 1) para validar el contenido y, si el volumen es grande, en su propio filegroup para poder respaldarlo aparte.
¿Cuál es el tipo de dato JSON en SQL Server?
Hasta SQL Server 2022 se usa NVARCHAR(MAX) con un CHECK ISJSON. SQL Server 2025 incorpora un tipo JSON nativo binario, más compacto que el texto.
¿Desde qué versión de SQL Server hay soporte para JSON?
Las funciones JSON (ISJSON, JSON_VALUE, OPENJSON, FOR JSON) existen desde SQL Server 2016. En versiones anteriores no hay soporte nativo: el JSON se maneja como texto plano, sin validación ni funciones de la base.
¿JSON o XML en SQL Server, qué conviene más?
Para datos semiestructurados, JSON como NVARCHAR(MAX) pesa menos y se consulta más simple que XML, que arrastra su propio overhead de parsing. Si estás arrancando un diseño nuevo, no hay razón para elegir XML salvo que ya tengas ese ecosistema construido.
¿Qué rendimiento tiene OPENJSON en SQL Server?
Sirve para desarmar JSON en columnas relacionales una vez, al escribir o migrar datos. Usarlo para filtrar en cada consulta del día a día no escala: no hay estadísticas ni índices que lo ayuden.
¿Se puede indexar una columna NVARCHAR(MAX) con JSON?
No como índice directo. La alternativa es una columna calculada PERSISTED sobre la propiedad que filtrás, pero si llegaste a necesitar eso, esa propiedad debería ser una columna común.
¿Por qué SQL Server anda lento con tablas que tienen JSON?
Porque es un motor row-store: lee la página completa de 8 KB aunque la consulta no pida las columnas JSON. Filas más anchas significan menos filas por página y más lecturas lógicas.
¿Necesitás ayuda con el diseño de tu base SQL Server?
En Triggerdb Consulting ayudamos a empresas a resolver problemas críticos de SQL Server, Power BI y Microsoft Fabric.
👉 Solicitar diagnóstico de diseño de base de datos — contanos tu problema y te decimos cómo podemos ayudarte.
Ejemplos
Te comparto en mi Github los ejemplos que use para este post Scripts de ejemplos

