模式匹配查询的性能调整
Performance tuning on pattern matching query
我有两个 table:
Table 1: 100 行
Table 2:1000 万行
示例:
Table 1: tb100
create table tb100
(
name varchar(50)
);
insert into tb100 values('Mak John'),('Will Smith'),('Luke W')......100 rows.
Table 2: tb10mil
create table tb10mil
(
name varchar(50)
);
insert into tb10mil values('John A Mak'),('K Smith Will'),('James Henry')......10 millions rows.
create nonclustered index nci_tb10mil_name on tb10mil(name);
注意:我想匹配两个 table 之间的名称,如果 WORD(John,Smith,Will) 出现在另一个 table 中.例如 John
出现在 John A Mark
.
中
我的尝试:
首先,我创建了用户定义函数,用于将 tb100
的 name
拆分成行。
函数:udf_Split
CREATE FUNCTION [dbo].[udf_Split]
(
@InputString VARCHAR(8000),
@Delimiter VARCHAR(50)
)
RETURNS @Items TABLE (ID INTEGER IDENTITY(1,1), Item VARCHAR(8000))
AS
BEGIN
IF @Delimiter = ' '
BEGIN
SET @Delimiter = ','
SET @InputString = REPLACE(@InputString, ' ', @Delimiter)
END
IF (@Delimiter IS NULL OR @Delimiter = '')
SET @Delimiter = ','
DECLARE @Item VARCHAR(8000)
DECLARE @ItemList VARCHAR(8000)
DECLARE @DelimIndex INT
SET @ItemList = @InputString
SET @DelimIndex = CHARINDEX(@Delimiter, @ItemList, 0)
WHILE (@DelimIndex != 0)
BEGIN
SET @Item = SUBSTRING(@ItemList, 0, @DelimIndex)
INSERT INTO @Items VALUES (@Item)
SET @ItemList = SUBSTRING(@ItemList, @DelimIndex+1, LEN(@ItemList)-@DelimIndex)
SET @DelimIndex = CHARINDEX(@Delimiter, @ItemList, 0)
END
IF @Item IS NOT NULL
BEGIN
SET @Item = @ItemList
INSERT INTO @Items VALUES (@Item)
END
ELSE INSERT INTO @Items VALUES (@InputString)
RETURN
END
然后我写了以下查询:
;with splitdata as
(
select f.item as data
from tb100 t
cross apply dbo.udf_split(t.name,' ') f
)
select t2.name
from tb10mil t2
inner join splitdata c on charindex(c.data,t2.name)>0
group by t2.name
以上查询执行时间超过 20 分钟。
您可以像下面这样尝试。
;WITH splitdata
AS (SELECT splitname
FROM (SELECT *,
Cast('<X>' + Replace(F.Name, ' ', '</X><X>') + '</X>' AS XML)
AS xmlfilter
FROM tb100 F)F1
CROSS apply (SELECT fdata.d.value('.', 'varchar(50)') AS splitName
FROM f1.xmlfilter.nodes('X') AS fdata(d)) O)
SELECT DISTINCT t2.NAME
FROM tb10mil t2
INNER JOIN splitdata S
ON T2.NAME LIKE '%' + S.splitname + '%'
让我们谈谈性能
第一点是:尽量避免
标量函数,并尽可能避免使用 多语句 TVF。唯一的
快速方法是 inline-TVF(one-line-statement)。
第二点:尽可能避免循环!
第三点(实际上是第一点):尝试将数据存储在
为快速查询优化的格式。存储多个值
一个单元格内违反 1NF 和巨大的速度杀手。
你可以试试这个:
模拟您的 tables
CREATE TABLE #t100(ID INT IDENTITY,SomeName VARCHAR(200));
CREATE TABLE #t1M (ID INT IDENTITY,SomeName VARCHAR(200));
INSERT INTO #t100 VALUES('james smith'),('mak john'),('Luke W');
GO
INSERT INTO #t1M values('John A Mak'),('K Smith Will'),('James Henry'),('James John'),('Some other');
GO
--创建tables spearately 存储名称片段(这是您实际应该使用的格式)
CREATE TABLE #t100Splitted(ID INT IDENTITY PRIMARY KEY,ID_t100 INT,Fragment NVARCHAR(200));
--Use an inline XML-splitter
INSERT INTO #t100Splitted(ID_t100,Fragment)
SELECT ID
,B.frg.value('text()[1]','nvarchar(200)')
FROM #t100
CROSS APPLY(SELECT CAST('<x>' + REPLACE((SELECT SomeName AS [*] FOR XML PATH('')),' ','</x><x>') + '</x>' AS XML)) A(CastedToXml)
CROSS APPLY A.CastedToXml.nodes('/x[text()]') B(frg);
--add indexes
CREATE INDEX IX_t100_ID_t100 ON #t100Splitted(ID_t100);
CREATE INDEX IX_t100_Fragment ON #t100Splitted(Fragment);
--The same for the second table
CREATE TABLE #t1MSplitted(ID INT IDENTITY PRIMARY KEY,ID_t1M INT,Fragment NVARCHAR(200));
INSERT INTO #t1MSplitted(ID_t1M,Fragment)
SELECT ID
,B.frg.value('text()[1]','nvarchar(200)')
FROM #t1M
CROSS APPLY(SELECT CAST('<x>' + REPLACE((SELECT SomeName AS [*] FOR XML PATH('')),' ','</x><x>') + '</x>' AS XML)) A(CastedToXml)
CROSS APPLY A.CastedToXml.nodes('/x[text()]') B(frg);
CREATE INDEX IX_tM_ID_t100 ON #t1MSplitted(ID_t1M);
CREATE INDEX IX_tM_Fragment ON #t1MSplitted(Fragment);
GO
- Check the intermediate results
SELECT * FROM #t100Splitted;
SELECT * FROM #t1MSplitted;
GO
--该查询将return具有共同片段的所有行
--您可以重新加入您的来源-tables 以取回值
--您可以按 t2.ID_t100 分组以获得较小 table 的 ID(更快)
SELECT t1.ID_t1M
FROM #t1MSplitted t1
INNER JOIN #t100Splitted t2 ON t1.Fragment=t2.Fragment
GROUP BY t1.ID_t1M
GO
--清理
DROP TABLE #t100;
GO
DROP TABLE #t1M;
GO
DROP TABLE #t100Splitted;
GO
DROP TABLE #t1MSplitted;
GO
在我的系统上,大约 2 分钟内处理了 1 个 Mio 行。
更新 - 100 行与 1000 万行的性能测试
(点击率非常高)
只是拆分,将您的数据转换成更好的形状:~17 分钟
最终select(只是查找):<1分钟
最终select但是找小table的ID:几秒
(转换数据后,这将是 正常速度)
PSK 方法(带内联拆分):~ 30 分钟
我试图保存内存,从而通过避免连接来节省处理内存的时间。
我试图用较小的 table.
中 4
值的 ~420k
记录来模拟你的问题
方法是为了避免连接和限制问题内存space从m x n
到至少更大的m&n。
select DISTINCT t2.name
from tb10mil t2
where (SELECT TOP(1) 1 FROM #splitdata where CHARINDEX(data,t2.Problem)>0)=1
结果: 所用方法所用时间减半。
(reduced from ~28 s to ~14s)
CON: 仅当 table 之一相当小时,方法才有用
我有两个 table:
Table 1: 100 行
Table 2:1000 万行
示例:
Table 1: tb100
create table tb100
(
name varchar(50)
);
insert into tb100 values('Mak John'),('Will Smith'),('Luke W')......100 rows.
Table 2: tb10mil
create table tb10mil
(
name varchar(50)
);
insert into tb10mil values('John A Mak'),('K Smith Will'),('James Henry')......10 millions rows.
create nonclustered index nci_tb10mil_name on tb10mil(name);
注意:我想匹配两个 table 之间的名称,如果 WORD(John,Smith,Will) 出现在另一个 table 中.例如 John
出现在 John A Mark
.
我的尝试:
首先,我创建了用户定义函数,用于将 tb100
的 name
拆分成行。
函数:udf_Split
CREATE FUNCTION [dbo].[udf_Split]
(
@InputString VARCHAR(8000),
@Delimiter VARCHAR(50)
)
RETURNS @Items TABLE (ID INTEGER IDENTITY(1,1), Item VARCHAR(8000))
AS
BEGIN
IF @Delimiter = ' '
BEGIN
SET @Delimiter = ','
SET @InputString = REPLACE(@InputString, ' ', @Delimiter)
END
IF (@Delimiter IS NULL OR @Delimiter = '')
SET @Delimiter = ','
DECLARE @Item VARCHAR(8000)
DECLARE @ItemList VARCHAR(8000)
DECLARE @DelimIndex INT
SET @ItemList = @InputString
SET @DelimIndex = CHARINDEX(@Delimiter, @ItemList, 0)
WHILE (@DelimIndex != 0)
BEGIN
SET @Item = SUBSTRING(@ItemList, 0, @DelimIndex)
INSERT INTO @Items VALUES (@Item)
SET @ItemList = SUBSTRING(@ItemList, @DelimIndex+1, LEN(@ItemList)-@DelimIndex)
SET @DelimIndex = CHARINDEX(@Delimiter, @ItemList, 0)
END
IF @Item IS NOT NULL
BEGIN
SET @Item = @ItemList
INSERT INTO @Items VALUES (@Item)
END
ELSE INSERT INTO @Items VALUES (@InputString)
RETURN
END
然后我写了以下查询:
;with splitdata as
(
select f.item as data
from tb100 t
cross apply dbo.udf_split(t.name,' ') f
)
select t2.name
from tb10mil t2
inner join splitdata c on charindex(c.data,t2.name)>0
group by t2.name
以上查询执行时间超过 20 分钟。
您可以像下面这样尝试。
;WITH splitdata
AS (SELECT splitname
FROM (SELECT *,
Cast('<X>' + Replace(F.Name, ' ', '</X><X>') + '</X>' AS XML)
AS xmlfilter
FROM tb100 F)F1
CROSS apply (SELECT fdata.d.value('.', 'varchar(50)') AS splitName
FROM f1.xmlfilter.nodes('X') AS fdata(d)) O)
SELECT DISTINCT t2.NAME
FROM tb10mil t2
INNER JOIN splitdata S
ON T2.NAME LIKE '%' + S.splitname + '%'
让我们谈谈性能
第一点是:尽量避免 标量函数,并尽可能避免使用 多语句 TVF。唯一的 快速方法是 inline-TVF(one-line-statement)。
第二点:尽可能避免循环!
第三点(实际上是第一点):尝试将数据存储在 为快速查询优化的格式。存储多个值 一个单元格内违反 1NF 和巨大的速度杀手。
你可以试试这个:
模拟您的 tables
CREATE TABLE #t100(ID INT IDENTITY,SomeName VARCHAR(200));
CREATE TABLE #t1M (ID INT IDENTITY,SomeName VARCHAR(200));
INSERT INTO #t100 VALUES('james smith'),('mak john'),('Luke W');
GO
INSERT INTO #t1M values('John A Mak'),('K Smith Will'),('James Henry'),('James John'),('Some other');
GO
--创建tables spearately 存储名称片段(这是您实际应该使用的格式)
CREATE TABLE #t100Splitted(ID INT IDENTITY PRIMARY KEY,ID_t100 INT,Fragment NVARCHAR(200));
--Use an inline XML-splitter
INSERT INTO #t100Splitted(ID_t100,Fragment)
SELECT ID
,B.frg.value('text()[1]','nvarchar(200)')
FROM #t100
CROSS APPLY(SELECT CAST('<x>' + REPLACE((SELECT SomeName AS [*] FOR XML PATH('')),' ','</x><x>') + '</x>' AS XML)) A(CastedToXml)
CROSS APPLY A.CastedToXml.nodes('/x[text()]') B(frg);
--add indexes
CREATE INDEX IX_t100_ID_t100 ON #t100Splitted(ID_t100);
CREATE INDEX IX_t100_Fragment ON #t100Splitted(Fragment);
--The same for the second table
CREATE TABLE #t1MSplitted(ID INT IDENTITY PRIMARY KEY,ID_t1M INT,Fragment NVARCHAR(200));
INSERT INTO #t1MSplitted(ID_t1M,Fragment)
SELECT ID
,B.frg.value('text()[1]','nvarchar(200)')
FROM #t1M
CROSS APPLY(SELECT CAST('<x>' + REPLACE((SELECT SomeName AS [*] FOR XML PATH('')),' ','</x><x>') + '</x>' AS XML)) A(CastedToXml)
CROSS APPLY A.CastedToXml.nodes('/x[text()]') B(frg);
CREATE INDEX IX_tM_ID_t100 ON #t1MSplitted(ID_t1M);
CREATE INDEX IX_tM_Fragment ON #t1MSplitted(Fragment);
GO
- Check the intermediate results
SELECT * FROM #t100Splitted;
SELECT * FROM #t1MSplitted;
GO
--该查询将return具有共同片段的所有行
--您可以重新加入您的来源-tables 以取回值
--您可以按 t2.ID_t100 分组以获得较小 table 的 ID(更快)
SELECT t1.ID_t1M
FROM #t1MSplitted t1
INNER JOIN #t100Splitted t2 ON t1.Fragment=t2.Fragment
GROUP BY t1.ID_t1M
GO
--清理
DROP TABLE #t100;
GO
DROP TABLE #t1M;
GO
DROP TABLE #t100Splitted;
GO
DROP TABLE #t1MSplitted;
GO
在我的系统上,大约 2 分钟内处理了 1 个 Mio 行。
更新 - 100 行与 1000 万行的性能测试
(点击率非常高)
只是拆分,将您的数据转换成更好的形状:~17 分钟
最终select(只是查找):<1分钟
最终select但是找小table的ID:几秒
(转换数据后,这将是 正常速度)PSK 方法(带内联拆分):~ 30 分钟
我试图保存内存,从而通过避免连接来节省处理内存的时间。 我试图用较小的 table.
中4
值的 ~420k
记录来模拟你的问题
方法是为了避免连接和限制问题内存space从m x n
到至少更大的m&n。
select DISTINCT t2.name
from tb10mil t2
where (SELECT TOP(1) 1 FROM #splitdata where CHARINDEX(data,t2.Problem)>0)=1
结果: 所用方法所用时间减半。
(reduced from ~28 s to ~14s)
CON: 仅当 table 之一相当小时,方法才有用