索引 JSON 数据

适用于: SQL Server 2016 (13.x) 及以后版本 Azure SQL 数据库Azure SQL 托管实例Microsoft Fabric 中的 SQL 数据库

你可以通过使用标准的B树索引来优化JSON文档上的查询。 数据库索引可提高筛选和排序操作的性能。 没有索引,数据库引擎 每次查询数据都必须进行一次完整的表扫描。

Tip

索引在 varchar/nvarchar 或原生 json 数据类型中的 JSON 数据上的工作原理相同。

Note

json 数据类型和 JSON 索引 已在 SQL Server 2025 (17.x)、Azure SQL 数据库、Azure SQL 托管实例(采用 SQL Server 2025 或始终保持最新更新策略)以及 Microsoft Fabric SQL 数据库中正式推出。

根据你存储的方式,有两种方法可以索引JSON数据:

索引类型 Description
JSON 索引 当你把 JSON 存储在 JSON 列时,可以在列上创建一个 JSON 索引 。 单个 JSON 索引可以优化许多使用诸如 JSON_VALUE、JSON_PATH_EXISTS 和 JSON_CONTAINS 等函数在文档中搜索值的查询。 这种方法推荐用于 json 数据类型。
计算出的列索引 当你把 JSON 存储为 varchar 或 nvarchar 文本,或者想把单个 JSON 属性索引为强类型列时,使用 JSON_VALUE 创建一个计算列,并在该列上构建标准索引。

用 JSON 索引索引 JSON 列

适用于:Azure SQL 数据库、Azure SQL 托管实例、SQL Server 2025 (17.x)以及 Microsoft Fabric 中的 SQL 数据库

你可以直接在 json 列上创建 JSON 索引。 JSON 索引索引 JSON 文档中的路径,因此单个索引可以优化文档中的多次搜索,而无需为每个属性单独创建一个计算列。

Note

SQL Server 2022(16.x)及更早版本不支持 JSON 索引。

以下示例创建了一个带有 JSON 列和该列 JSON 索引的表:

CREATE TABLE Orders
(
    OrderID INT IDENTITY PRIMARY KEY,
    OrderDetails JSON NOT NULL
);

CREATE JSON INDEX IX_OrderDetails
    ON Orders (OrderDetails);

有了 JSON 索引,数据库引擎 可以利用它来优化使用 JSON_VALUE、 JSON_PATH_EXISTS或 JSON_CONTAINS的查询。 例如,以下查询可以使用JSON索引按客户名称筛选订单:

SELECT OrderID
FROM Orders
WHERE JSON_VALUE(OrderDetails, '$.Customer.Name') = N'Aaron Campbell';

关于完整的语法、索引选项以及支持的谓词列表,请参见 CREATE JSON INDEX。

通过使用计算列对 JSON 属性编制索引

当你以 varchar 或 nvarchar 文本形式存储 JSON,或者想将单个 JSON 属性作为强类型列进行过滤或排序时,使用计算列索引。 通常你希望通过JSON文档的一个或多个 属性 来筛选或排序查询结果。

Example

此示例假定 AdventureWorks.SalesOrderHeader 表包含 Info 列,此列包含关于销售订单的采用 JSON 格式的各种信息。 例如,它包含客户、销售人员、装运和帐单邮寄地址等非结构化数据。 你可以使用 Info 列中的值来筛选客户的销售订单。

默认情况下,使用的列 Info 不存在,可以使用以下代码在 AdventureWorks 数据库中创建它。 以下示例不适用于 AdventureWorksLT 一系列示例数据库。

IF NOT EXISTS (SELECT *
               FROM sys.columns
               WHERE object_id = OBJECT_ID('[Sales].[SalesOrderHeader]')
                     AND name = 'Info')
    ALTER TABLE [Sales].[SalesOrderHeader]
        ADD [Info] NVARCHAR (MAX) NULL;
GO

UPDATE h
SET [Info] =
(
    SELECT concat(p.FirstName, N' ', p.LastName) AS [Customer.Name],
            p.BusinessEntityID AS [Customer.ID],
            p.[PersonType] AS [Customer.Type],
            soh.SalesOrderID AS [Order.ID],
            soh.SalesOrderNumber AS [Order.Number],
            soh.OrderDate AS [Order.CreationDate],
            soh.TotalDue AS [Order.TotalDue]
    FROM [Sales].SalesOrderHeader AS soh
        INNER JOIN [Sales].[Customer] AS c
            ON c.CustomerID = soh.CustomerID
        INNER JOIN [Person].[Person] AS p
            ON p.BusinessEntityID = c.CustomerID
    WHERE soh.SalesOrderID = h.SalesOrderID
    FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
)
FROM [Sales].SalesOrderHeader AS h;

要优化的查询

以下示例说明想要使用索引进行优化的查询类型。

SELECT SalesOrderNumber,
       OrderDate,
       JSON_VALUE(Info, '$.Customer.Name') AS CustomerName
FROM Sales.SalesOrderHeader
WHERE JSON_VALUE(Info, '$.Customer.Name') = N'Aaron Campbell';

示例索引

如果想要对 JSON 文档中的某个属性加快筛选或 ORDER BY 子句的速度,你可以使用已用于其他列的索引。 但是,不能 直接 引用 JSON 文档中的属性。

  1. 首先创建一个“虚拟列”,用于返回你要用于筛选的值。
  2. 然后,对该虚拟列创建索引。

下面的示例创建可用于编制索引的计算列。 然后对此新计算列创建索引。 此示例创建一个公开客户名称的列,客户名称存储在 $.Customer.Name 路径中的 JSON 数据内。

ALTER TABLE Sales.SalesOrderHeader
    ADD vCustomerName AS JSON_VALUE(Info, '$.Customer.Name');

CREATE INDEX idx_soh_json_CustomerName
    ON Sales.SalesOrderHeader(vCustomerName);

此语句将返回以下警告:

Warning! The maximum key length for a nonclustered index is 1700 bytes.
The index 'vCustomerName' has maximum length of 8000 bytes.
For some combination of large values, the insert/update operation will fail.

该 JSON_VALUE 函数可能返回最多 8000 字节的文本值(例如,作为 nvarchar(4000) 类型)。 但是,不能为超过 1700 字节的值编制索引。 如果尝试在索引计算列中输入超过 1700 字节的值,数据操作语言 (DML) 操作将失败。

为了提升性能,请尝试将使用计算列公开的值强制转换为最小的适用数据类型。 使用 int 和 datetime2 类型,而不是字符串类型。

关于计算列的详细信息

计算列不是永久性的。 仅当需要重新生成索引时,才会对计算列进行计算。 它不会占用表中的额外空间。

请务必使用你计划在查询中使用的相同表达式来创建计算列 - 在此示例中,此表达式为 JSON_VALUE(Info, '$.Customer.Name')。

你无须重新编写查询。 如果你将表达式与 JSON_VALUE 函数配合使用,如前面的示例查询中所示,则 SQL Server 会认为存在具有相同表达式的等效计算列,并会在可能的情况下应用索引。

此示例的执行计划

下面是此示例中的查询执行计划。

屏幕截图显示此示例的执行计划。

SQL Server 不使用全表扫描,而是使用索引查找非聚集索引并查找满足指定条件的行。 然后它在 SalesOrderHeader 表中使用键查找来提取查询中引用的其他列 - 在此示例中为 SalesOrderNumber 和 OrderDate。

通过包含的列进一步优化索引

如果在索引中添加所需的列,则可以避免在表中进行这种额外的查找。 你可以将这些列添加为标准包含列,如以下示例中所示,这是对前面的 CREATE INDEX 示例的延伸。

CREATE INDEX idx_soh_json_CustomerName
    ON Sales.SalesOrderHeader(vCustomerName)
    INCLUDE(SalesOrderNumber, OrderDate);

在这种情况下,SQL Server 不必从 SalesOrderHeader 表中读取更多数据,因为它所需的全部内容都包含在非聚集 JSON 索引中。 这种类型的索引是将查询中的 JSON 和列数据结合起来并为工作负载创建最佳索引的好方法。

计算出的列索引是排序规则感知的

JSON 数据上的计算列索引的一个重要功能是它们可以识别排序规则。 创建计算列时使用的 JSON_VALUE 函数的结果是从输入表达式继承其排序规则的文本值。 因此,将使用源列中定义的排序规则对索引中的值进行排序。

为了证明索引是排序感知索引,以下示例创建了一个具有主键和 JSON 内容的简单集合表。

CREATE TABLE JsonCollection
(
    id INT IDENTITY CONSTRAINT PK_JSON_ID PRIMARY KEY,
    [json] NVARCHAR (MAX) COLLATE SERBIAN_CYRILLIC_100_CI_AI
        CONSTRAINT [Content should be formatted as JSON] CHECK (ISJSON(json) > 0)
);

前一个命令指定 json 列的塞尔维亚西里尔文排序规则。 后面的示例填充此表,并对名称属性创建索引。

INSERT INTO JsonCollection
VALUES (N'{"name":"Иво","surname":"Андрић"}'),
       (N'{"name":"Андрија","surname":"Герић"}'),
       (N'{"name":"Владе","surname":"Дивац"}'),
       (N'{"name":"Новак","surname":"Ђоковић"}'),
       (N'{"name":"Предраг","surname":"Стојаковић"}'),
       (N'{"name":"Михајло","surname":"Пупин"}'),
       (N'{"name":"Борислав","surname":"Станковић"}'),
       (N'{"name":"Владимир","surname":"Грбић"}'),
       (N'{"name":"Жарко","surname":"Паспаљ"}'),
       (N'{"name":"Дејан","surname":"Бодирога"}'),
       (N'{"name":"Ђорђе","surname":"Вајферт"}'),
       (N'{"name":"Горан","surname":"Бреговић"}'),
       (N'{"name":"Милутин","surname":"Миланковић"}'),
       (N'{"name":"Никола","surname":"Тесла"}');
GO

ALTER TABLE JsonCollection
    ADD vName AS JSON_VALUE(json, '$.name');

CREATE INDEX idx_name
    ON JsonCollection(vName);

前面的命令对计算列 vName 创建标准索引,它表示 JSON $.name 属性中的值。 在塞尔维亚西里尔文代码页中,字母的顺序是 А、Б、В、Г、Д、Ђ、Е 等。索引中的项的顺序符合塞尔维亚西里尔文规则,因为 JSON_VALUE 函数的结果从源列继承其排序规则。 以下示例查询此集合并按名称对结果进行排序。

SELECT JSON_VALUE(json, '$.name'),
       *
FROM JsonCollection
ORDER BY JSON_VALUE(json, '$.name');

如果查看实际的执行计划,会发现它使用非聚集索引中经过排序的值。

显示使用非聚集索引中已排序值的执行计划的屏幕截图。

虽然查询具有 ORDER BY 子句,但执行计划不使用 Sort 运算符。 JSON 索引已按照塞尔维亚西里尔文规则排序。 因此 SQL Server 可使用其中的结果已经过排序的非聚集索引。

但是,如果更改 ORDER BY 表达式的排序规则(例如,如果将 COLLATE French_100_CI_AS_SC 添加到 JSON_VALUE 函数之后),会获得其他查询执行计划。

显示其他执行计划的屏幕截图。

由于索引中的值顺序不符合法国排序规则,因此 SQL Server 无法使用索引对结果进行排序。 因此,它会添加一个使用法语排序规则对结果进行排序的 Sort 运算符。

Microsoft视频

有关内置 JSON 支持的视觉简介,请参阅以下视频: