模式匹配查询的性能优化

时间:2019-01-24 07:52:23

标签: sql-server tsql sql-server-2008-r2 database-performance

我有两个表:

表1:100行

表2:1000万行

示例:

表1:tb100

create table tb100
(
    name varchar(50)
);

insert into tb100 values('Mak John'),('Will Smith'),('Luke W')......100 rows.

表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);

注意:如果另一个表中存在任何 WORD (John,Smith,Will),我想匹配两个表之间的名称。例如John中出现的John A Mark

我的尝试:

首先,我创建了用户定义函数,用于将name的{​​{1}}分成几行。

功能:tb100

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 

以上查询需要20余分钟才能执行。

3 个答案:

答案 0 :(得分:4)

我们来谈谈性能

  • 第一点是:尽量避免 标量函数,并尽可能避免使用 multi-statement-TVF 。唯一的 快速方法是 inline-TVF (一种单行声明)。

  • 第二点:尽可能避免循环!

  • 第三点(实际上是第一点):尝试将数据存储在 为快速查询优化的格式。储存多个价值 一个单元格内的细胞违反了1NF和巨大的速度杀手。

您可以尝试以下方法:

模拟表格

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 

-创建表以清晰地存储名称片段(这实际上是您应该使用的格式)

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

-此查询将返回所有带有公共片段的行
-您可以重新加入源表以获取值
-您可以按t2.ID_t100分组以获取较小表的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行。

UPDATE-100行与1000万行的性能测试

(点击率很高)

  • 只需拆分,即可将数据转换为更好的形状:〜17分钟

  • 最终选择(只是查找): <1分钟

  • 最终选择,但查找小表的ID:几秒钟
    (一旦转换了数据,这将是正常速度

  • PSK的方法(带内联拆分):约30分钟

答案 1 :(得分:2)

您可以尝试以下操作。

  ;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 + '%' 

答案 2 :(得分:0)

我试图保存内存,从而避免了加入,从而节省了处理内存的时间。 我试图在较小表中的~420k值的4记录上模拟您的问题。

方法是为了避免合并,并将问题内存空间从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::仅当其中一个表很小时,这种方法才有用