加入 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;
对于这类问题还有其他更复杂的方法,但根据您提供的详细信息,我会采用这种方法。
我正在尝试优化 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;
对于这类问题还有其他更复杂的方法,但根据您提供的详细信息,我会采用这种方法。