加入 charindex 很慢

join over charindex is slow

我正在尝试优化 SQL Server 2017 中的 T-SQL 程序。我无法直接访问服务器,因此无法更改任何设置,也无法启用 FullTextSearch .

存在两个 table,一个带有关键字列表 (varchar(50)) 和数字代码 (int),另一个带有大型自由输入文本 (varchar(max)) 和 ID (int)。我想标记所有这些文本,其中关键字包含在另一个 table 中。使用游标,它的工作方式如下:

CREATE PROCEDURE markTexts 
AS
BEGIN
    DECLARE @code INT;
    DECLARE @keyword VARCHAR(50);

    DECLARE kcodes CURSOR LOCAL FAST_FORWARD FOR
        SELECT code, keyword
        FROM KeywordCode
        ORDER BY code ASC;

    OPEN kcodes;
  
    FETCH NEXT FROM kcodes INTO @code, @keyword;

    WHILE @@FETCH_STATUS = 0 
    BEGIN
        INSERT INTO markedTexts (textid, keywordcode)
            SELECT t.id, @code AS keywordcode
            FROM fullTexts t
            WHERE CHARINDEX(@keyword, t.fullText) > 0;

        FETCH NEXT FROM kcodes INTO @code, @keyword;   
    END;

    CLOSE kcodes;
    DEALLOCATE kcodes;
END;

table KeywordCode 有 ~200 行,fullTexts 有~50k 行。 运行 该过程产生约 20k 个结果,大约需要 30 秒,这太慢了(至少我希望它运行得更快)。由于我不能使用 FullTextSearch(使用索引和所有这些可能会更快......),我认为集合方法可能比迭代游标方法更快。看起来像这样:

INSERT INTO markedTexts (textid, keywordcode)
    SELECT t.id, k.code 
    FROM fullTexts t 
    INNER JOIN KeywordCode k ON CHARINDEX(k.keyword, t.fullText) > 0;

它看起来更简单、更干净,而且确实产生了相同的结果...但它需要大约 7 分钟(大约 3.5 倍的时间)。

所以我的问题是:为什么 JOIN 方法比游标慢很多,有什么明显的方法可以加快速度吗?

首先,您不需要那个游标,我认为一个简单的连接就可以了。游标非常慢,通常没有必要。让我们关注插入的 SELECT 部分,因为这是另一个问题。首先是一些示例数据:

-- Sample Data
CREATE TABLE dbo.KeywordCode(code INT, keyword VARCHAR(100));
CREATE TABLE dbo.fullTexts(id INT PRIMARY KEY, FullTxt VARCHAR(500));

INSERT dbo.KeywordCode 
  VALUES(1,'yada yada'),(2,'blah'),(3,'abc'),(4,'xxx a');
INSERT dbo.fullTexts 
  VALUES(10,'...yada yada yada'),(11,'xxx'),(12,'xxx abc123'),(24,'blah blah');

1.解决游标问题

这应该比使用游标更快地获得相同的结果:

-- INSERT INTO markedTexts (textid, keywordcode)
SELECT t.id, k.keyword
FROM   dbo.KeywordCode AS k
JOIN   dbo.fullTexts   AS t
  ON   CHARINDEX(k.keyword,t.FullTxt) > 0;

2。解决 SARGability 问题

在过滤器中使用 CHARINDEX,例如 WHERE 或 JOIN 子句,进行查询 non-SARGable 这意味着执行引擎将无法对可用索引执行查找,从而导致完整的 table 每次查询运行时扫描。注意上面 SELECT 查询的执行计划:

虽然我们已经解决了光标问题,但我们仍然要避免这种扫描。如果可能的话,我建议使用索引视图。

CREATE VIEW dbo.kw_txt WITH SCHEMABINDING AS
SELECT t.id, k.keyword
FROM   dbo.KeywordCode AS k
JOIN   dbo.fullTexts   AS t ON CHARINDEX(k.keyword,t.FullTxt) > 0;

现在,有了索引视图,您将获得此计划,其中包含为您预先加入的值。

此方法的特别之处在于您将如何获得此改进的计划,该计划利用索引视图,甚至无需在查询中引用视图。请注意,这两个查询都将在我们的新视图上利用索引:

SELECT t.id, t.keyword FROM dbo.kw_txt AS t;

以及:

SELECT t.id, k.keyword
FROM   dbo.KeywordCode AS k
JOIN   dbo.fullTexts   AS t ON CHARINDEX(k.keyword,t.FullTxt) > 0;

对于这类问题还有其他更复杂的方法,但根据您提供的详细信息,我会采用这种方法。