如何删除大表的嵌套循环连接

Rob*_*Sun 1 sql sql-server nested-loops database-performance sql-server-2012

SQL Server中有3个数据量很大的表,每个表包含大约100000行。有一个 SQL 从三个表中获取行。它的性能非常糟糕。

WITH t1 AS 
(
    SELECT 
        LeadId, dbo.get_item_id(Log) AS ItemId, DateCreated AS PriceDate
    FROM 
        (SELECT 
             t.ID, t.LeadID, t.Log, t.DateCreated, f.AskingPrice
         FROM 
             t
         JOIN 
             f ON f.PKID = t.LeadID
         WHERE 
             t.Log LIKE '%xxx%') temp
)
SELECT COUNT(1)
FROM t1
JOIN s ON s.ItemID = t1.ItemId
Run Code Online (Sandbox Code Playgroud)

在检查其估计执行计划时,我发现它使用了大行的嵌套循环连接。抢劫看下面的截图。图像中的顶部部分返回 124277 行,底部部分执行了 124277 次!我想这就是它这么慢的原因。

在此输入图像描述

在此输入图像描述

我们知道嵌套循环在处理大数据时存在很大的性能问题。如何删除它,并使用散列连接或其他连接代替?

编辑:以下是相关功能。

CREATE FUNCTION [dbo].[get_item_Id](@message VARCHAR(200))
RETURNS VARCHAR(200) AS
BEGIN
    DECLARE @result VARCHAR(200),
            @index int

    --Sold in eBay (372827580038).
    SELECT @index = PatIndex('%([0-9]%)%', @message)
    IF(@index = 0)
     SELECT @result='';
    ELSE 
     SELECT @result= REPLACE(REPLACE(REPLACE(SUBSTRING(@message, PatIndex('%([0-9]%)%', @message),8000), '.', ''),'(',''),')','')
    -- Return the result of the function
    RETURN @result
END;
Run Code Online (Sandbox Code Playgroud)

Mar*_*ith 5

由于某种原因,它决定s cross join t1先评估函数(结果别名为 Expr1002),然后对 [s].[ItemID]=[Expr1002] 进行过滤(而不是进行等值连接)。

它估计它将有88,969124,277行进入交叉连接(这意味着它将产生11,056,800,413

在交叉连接后执行标量 UDF 大约 110 亿次,然后向下过滤大约 110 亿行,这看起来确实很疯狂。如果在连接之前对其进行评估,则评估次数会少得多,并且也将是等连接,因此也可以使用HASHMERGE内部连接,并且只需读取所有表一次,而不会增加行计数。

我在本地重现了这一点,并且在创建 UDF 时行为发生了变化WITH SCHEMABINDING- SQL Server 将看到它不访问任何表,并且它的定义是确定性的。

跟踪标志8606输出似乎支持这个问题。在这两种情况下,“简化树”阶段将查询表示为与 ScalarUdf 上的谓词的交叉连接。标量 UDF 被注释为“IsDet”或“IsNonDet”,具体取决于函数是否受架构绑定。在前一种情况下,“项目规范化”阶段将计算推回连接之前,并为其提供在连接本身中引用的别名,在非确定性情况下,这种情况不会发生。

我强烈建议摆脱这个标量函数并将其替换为内联版本,尽管除此之外非内联标量函数还有许多众所周知的附加性能问题。

新函数将是

CREATE FUNCTION get_item_Id_inline (@message VARCHAR(200))
RETURNS TABLE
AS
    RETURN
      (SELECT item_Id = CASE
                          WHEN PatIndex('%([0-9]%)%', @message) = 0 THEN ''
                          ELSE REPLACE(REPLACE(REPLACE(SUBSTRING(@message, PatIndex('%([0-9]%)%', @message), 8000), '.', ''), '(', ''), ')', '')
                        END) 
Run Code Online (Sandbox Code Playgroud)

并重写查询

WITH t1
     AS (SELECT t.LeadID,
                i.item_Id     AS ItemId,
                t.DateCreated AS PriceDate
         FROM   t
                CROSS apply dbo.get_item_Id_inline(t.Log) i
                JOIN f
                  ON f.PKID = t.LeadID
         WHERE  t.Log LIKE '%xxx%')
SELECT COUNT(1)
FROM   t1
       JOIN s
         ON s.ItemID = t1.ItemId 
Run Code Online (Sandbox Code Playgroud)

可能仍然有一些额外优化的空间,但这将比您当前的执行计划好几个数量级(因为这是灾难性的糟糕)。