10.11.25

Новое в SQL Server 2025: JSON‑индексы

Автор: Koen Verbeeck, The New JSON Index in SQL Server 2025

Мы начали использовать новый тип данных 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.objects, можно увидеть, что JSON‑индекс хранится как внутренняя таблица.

Это значит, что при расчёте размера вашей таблицы через 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 использует индекс, так что это возможно.



Комментариев нет:

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