添加索引会加速字符串通配符 % 搜索吗?

Does Adding Indexes speed up String Wildcard % searches?

我们正在使用列字符串对数据库 table 进行通配符搜索。在列上创建非聚集索引是否有助于通配符搜索?这会提高性能吗?

CREATE TABLE [dbo].[Product](
    [ProductId] [int] NOT NULL,
    [ProductName] [varchar](250) NOT NULL,
    [ModifiedDate] [datetime] NOT NULL, 
    ...
    CONSTRAINT [PK_ProductId] PRIMARY KEY CLUSTERED 
    (
       [ProductId] ASC
    )
 )

建议索引:

CREATE NONCLUSTERED INDEX [IX_Product_ProductName] ON [dbo].[Product] [ProductName])

对于这个查询

select * from dbo.Product where ProductName like '%furniture%'

目前正在使用 Microsoft SQL Server 2019。

对于如图所示的双端通配符搜索,索引无法通过限制 SQL 服务器必须查看的行来帮助您 - 将执行完整的 table 扫描。但它可以帮助减少必须从磁盘检索的数据量。

因为在ProductName like '%furniture%'中,ProductName可以以任何字符串开始或结束,所以没有索引可以减少必须检查的行。

但是,如果 Product table 中的一行是 1,000 个字符,而您有 10,000 行,则必须加载那么多数据。但是如果你在 ProductName 上有一个索引,而 ProductName 只有 50 个字符,那么你只需要加载 10,000 * 50 而不是 10,000 * 1000.

注意:如果查询是在 'furniture%' 末尾使用 % 的单端通配符搜索,那么建议的索引肯定会有所帮助。

是的,第一个 % 之前的部分与索引匹配。当然,如果您的模式以 % 开头,则将改为执行完整扫描。

这取决于优化器。 Like 通常需要完整的 table 扫描。如果优化器可以扫描索引以查找匹配项,那么它将执行比完整 table 扫描更快的索引扫描。

如果优化器没有select索引扫描,您可以强制它使用索引。您必须测量性能时间以确定使用索引扫描是否会减少搜索时间

使用with (index(index_name))强制进行索引扫描,例如

select * from t1 with (index(t1i1)) where v1 like '456%'

SQL Server Index - Any improvement for LIKE queries?

如果您使用 %search% 模式,优化器将始终执行完整的 table 扫描。

另一种加快搜索速度的技术是使用子字符串和精确匹配搜索。

创建普通索引无济于事 (*),但 full-text index 会有所帮助,尽管您必须将查询更改为如下内容:

select * from dbo.Product where ProductName CONTAINS 'furniture'

(* -- 好吧,它可能 稍微 有帮助,因为它可以减少对 table 中每一行和每一列的扫描仅仅每一行和相关的列。但是,它不会达到我们通常期望从将扫描变成单次查找的索引中获得的数量级性能提升。)

首先,您可以使用 FTS 将单词搜索成句子,甚至部分搜索(以...开头)。

对于以你结尾或包含你的那些可以使用旋转索引技术:

CREATE TABLE T_WRD
(WRD_ID                BIGINT IDENTITY PRIMARY KEY,
 WRD_WORD              VARCHAR(64) COLLATE Latin1_General_100_BIN NOT NULL UNIQUE,
 WRD_DROW              AS REVERSE(WRD_WORD) PERSISTED NOT NULL UNIQUE,
 WRD_WORD2             VARCHAR(64) COLLATE Latin1_General_100_CI_AI NOT NULL) ;
GO
    
CREATE TABLE T_WORD_ROTATE_STRING_WRS
(WRD_ID                BIGINT NOT NULL REFERENCES T_WRD (WRD_ID),
 WRS_ROTATE            SMALLINT NOT NULL,
 WRD_ID_PART           BIGINT NOT NULL REFERENCES T_WRD (WRD_ID),
 PRIMARY KEY (WRD_ID,  WRS_ROTATE));
GO
    
CREATE OR ALTER TRIGGER E_I_WRD
ON T_WRD
FOR INSERT
AS
    
SET NOCOUNT ON;
    
-- splitting words
WITH R AS
(
SELECT WRD_ID, TRIM(WRD_WORD) AS WRD_WORD, 0 AS ROTATE
FROM   INSERTED
UNION ALL
SELECT WRD_ID, RIGHT(WRD_WORD, LEN(WRD_WORD) -1), ROTATE + 1
FROM   R
WHERE  LEN(WRD_WORD) > 1
)
SELECT *
INTO #WRD
FROM   R;
    
-- inserting missing words
INSERT INTO T_WRD (WRD_WORD, WRD_WORD2)
SELECT WRD_WORD, LOWER(WRD_WORD) COLLATE SQL_Latin1_General_CP1251_CI_AS
FROM   #WRD
WHERE  WRD_WORD NOT IN (SELECT WRD_WORD 
                        FROM T_WRD);
    
-- inserting cross reference words
INSERT INTO T_WORD_ROTATE_STRING_WRS
SELECT M.WRD_ID, ROTATE, D.WRD_ID
FROM   #WRD AS M
      JOIN T_WRD AS D
         ON M.WRD_WORD = D.WRD_WORD
WHERE  NOT EXISTS(SELECT 1/0
                  FROM   T_WORD_ROTATE_STRING_WRS AS S
                  WHERE  S.WRD_ID = M.WRD_ID
                  AND  S.WRS_ROTATE = ROTATE);
GO

然后现在您可以在第一个 table 中插入您想要从句子中提取的所有单词,并通过部分结束查询这两个 table 来找到它...

举个例子,单词:

WITH 
T AS (SELECT 'électricité' AS W)
INSERT INTO T_WRD 
SELECT W, LOWER(CAST(W AS VARCHAR(64)) COLLATE SQL_Latin1_General_CP1251_CI_AS) AS W2
FROM  T;

您现在可以使用:

SELECT * FROM T_WRD;
SELECT * FROM T_WORD_ROTATE_STRING_WRS;

找到那些部分单词