Мы начали использовать новый тип данных JSON в SQL Server для хранения JSON в таблице. При выполнении запросов с функциями вроде JSON_VALUE видим, что каждый раз выполняется полный просмотр таблицы. Хорошо бы индексировать JSON, чтобы повысить производительность. Поскольку JSON всё шире применяется в мире данных (например, REST‑API обычно возвращают наборы данных в формате JSON), SQL Server расширяет поддержку JSON прямо в ядре СУБД. В начале 2025 года в предварительной версии SQL Server 2025 появилось несколько новых функций для работы с JSON. Ещё одно дополнение — новый тип индекса: JSON‑индекс.
SQL Server 2025 JSON Index
В этой статье мы представим новый индекс и объясним, как его создавать и использовать в запросах. Статья написана по SQL Server 2025 Release Candidate 1 (предпросмотр). Помните: функциональность, показанная в предварительной версии, может измениться к моменту выхода финального релиза SQL Server 2025.
Чтобы ускорить выполнение запросов по столбцу с данными JSON, можно создать новый тип индекса — JSON‑индекс.
Синтаксис следующий:
CREATE JSON INDEX ix_json ON dbo.mytable(myjsoncolumn) FOR ('$.some.path', '$.another.path');
У JSON‑индекса есть ряд ограничений:
- на момент написания он доступен только в SQL Server 2025;
- у таблицы должен быть кластерный первичный ключ;
- индекс нельзя создать на представлении, табличной переменной или оптимизированной в памяти таблице;
- столбец, по которому создаётся индекс, должен иметь тип данных JSON (т. е. нельзя использовать VARCHAR(MAX)) и не может быть вычисляемым столбцом;
- на один столбец можно создать только один JSON‑индекс, но в нём допустимо указать несколько путей. Если в таблице несколько столбцов JSON, можно создать по одному индексу на каждый, всего до 249 JSON‑индексов. Это предел, а не целевая рекомендация;
- пока что параметры сжатия данных и подсказки индексов игнорируются.
Создание JSON‑индекса
Покажем на примере. В базе AdventureWorks создадим таблицу со столбцом JSON:
DROP TABLE IF EXISTS dbo.JsonTest;
CREATE TABLE dbo.JsonTest
(
Id INT IDENTITY(1,1) PRIMARY KEY
,CustomerKey INT
,Title NVARCHAR(8)
,FirstName NVARCHAR(50)
,MiddleName NVARCHAR(50)
,LastName NVARCHAR(50)
,Suffix NVARCHAR(10)
,EmailAddress NVARCHAR(50)
,Phone NVARCHAR(20)
,JsonData JSON);
С помощью предложения FOR JSON возьмём данные из измерения клиентов и преобразуем их в JSON. Результат вставим в таблицу.
INSERT INTO dbo.JsonTest(
CustomerKey
,Title
,FirstName
,MiddleName
,LastName
,Suffix
,EmailAddress
,Phone
,JsonData)
SELECT
CustomerKey
,Title
,FirstName
,MiddleName
,LastName
,Suffix
,EmailAddress
,Phone
,(
SELECT
CustomerKey AS [CustomerKey]
,Title AS [Name.Title]
,FirstName AS [Name.FirstName]
,MiddleName AS [Name.MiddleName]
,LastName AS [Name.LastName]
,Suffix AS [Name.Suffix]
,EmailAddress AS [ContactInfo.EmailAddress]
,Phone AS [ContactInfo.Phone]
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
) myjson
FROM AdventureWorksDW2022.dbo.DimCustomer;
GO 100
Чтобы в таблице было побольше данных, оператор INSERT выполняется 100 раз с помощью команды GO (работает только в SSMS или sqlcmd). Тот же скрипт выполняется для создания таблицы с именем dbo.JsonTest_Index. Наличие двух идентичных таблиц позволит сравнить производительность с JSON‑индексом и без него. Во второй таблице создадим следующий JSON‑индекс:
DROP INDEX IF EXISTS ix_json ON dbo.JsonTest_Index;
CREATE JSON INDEX ix_json ON dbo.JsonTest_Index(JsonData) FOR ('$.Name.FirstName');
Можно также создать индекс, поддерживающий несколько путей:
CREATE JSON INDEX ix_json ON dbo.JsonTest_Index(JsonData) FOR ('$.Name.FirstName', '$.ContactInfo.EmailAddress');
Вы могли бы указать пути '$.Name.FirstName', '$.Name.LastName', но поскольку SQL Server рекурсивно индексирует ключи и значения от указанного пути, разумнее задать просто '$.Name' (если только вы не хотите намеренно исключить атрибут отчества). SQL Server не позволяет указывать пересекающиеся пути: комбинация '$.Name.FirstName', '$.Name' приведёт к ошибке «Invalid JSON paths in JSON index.»
Это значит, что при расчёте размера вашей таблицы через sys.dm_db_partition_stats нужно учитывать и эту внутреннюю таблицу, чтобы получить корректный общий размер.
Запросы к JSON‑индексу
Протестируем новый индекс с функцией JSON_CONTAINS.
SET STATISTICS IO ON;
SELECT FirstName, LastName
FROM dbo.JsonTest
WHERE JSON_CONTAINS(JsonData,'David','$.Name.FirstName') = 1;
SELECT FirstName, LastName
FROM dbo.JsonTest_Index
WHERE JSON_CONTAINS(JsonData,'David','$.Name.FirstName') = 1;
Получаем следующие планы выполнения.
Верхний запрос обращается к таблице без JSON‑индекса и вынужден выполнять полный просмотр кластерного индекса. Нижний запрос уже может использовать JSON‑индекс. Однако, поскольку индекс реализован как отдельная (внутренняя) таблица, выполняется поиск по кластерному индексу этой внутренней таблицы, а затем результаты используются для поиска по кластерному индексу основной таблицы. По поведению это похоже на не покрывающий (non‑covering) индекс, которому требуется обращение по ключу к кластерному индексу, чтобы выбрать дополнительные столбцы.
Следовательно, в идеале JSON‑индекс должен возвращать относительно небольшое число строк, а не значительную часть таблицы.
Если посмотреть на число прочитанных страниц данных, видно, что использование JSON‑индекса существенно сокращает объём чтения: полное сканирование кластерного индекса даёт свыше 101 000 чтений, тогда как поиск по кластерному индексу JSON‑индекса — 48 чтений, а по кластерному индексу основной таблицы — около 17 000 чтений.
Изменим запросы и вместо JSON_CONTAINS используем JSON_VALUE.
SELECT FirstName, LastName
FROM dbo.JsonTest_Index
WHERE JSON_VALUE(JsonData,'$.Name.FirstName') = 'David';
По неизвестным причинам SQL Server не использует JSON‑индекс, хотя в документации указано, что такая поддержка есть.
Итоги
Не каждый оператор поддерживается в предварительной версии, но оператор сравнения = должен поддерживаться. Microsoft MVP Daniel Hutmacher в своей статье «JSON indexes in SQL Server: First impressions» приводит пример, где JSON_VALUE использует индекс, так что это возможно.






Комментариев нет:
Отправить комментарий