SQL Server嵌套循环运算符中的基数估计错误导致的性能问题

时间:2018-09-20 07:53:15

标签: sql-server performance nested-loops sql-execution-plan

我有一个SQL Server查询,其中一个INNER JOIN在一个包含一百万行(Acct)的大表和一个包含10,000行(AcctTxns)的小表之间,但是SQL Server生成的执行计划具有基数估计错误< / strong>。

我将问题归结为以下陈述:

SELECT p.AcctNo, p.Balance + t.TotalAmt as New Balance
    FROM Acct p JOIN AcctTxns t 
    ON p.AcctNo = t.AcctNo

Image - Nested Loops operator with wrong Estimated Number of Rows

Full Execution Plan

嵌套循环运算符显示的“估计行数”为16.2588,而“实际行数”为10000。

我正在使用MS SQL Server 2016(13.0.1742.0)。

我已经尝试了许多修复程序,包括:

  1. 更新统计信息
  2. 使用临时表获取中间结果
  3. 关闭2014年基数估计器
  4. 以多种不同方式重写SQL语句(这使我陷入了上述问题的核心)

但是他们不能解决问题。对嵌套循环级联的错误估计会产生tempDB,这会沿线溢出,从而影响性能。

有人遇到过类似的问题吗?希望能帮助您解决此问题。谢谢。

以下代码设置了问题:

--- [a] 1 million row Numbers table
DROP TABLE IF EXISTS #Numbers;
CREATE TABLE #Numbers (Number int PRIMARY KEY);
INSERT INTO #Numbers (Number) 
 SELECT TOP 1000000 ROW_NUMBER() OVER (ORDER BY (SELECT 1)) - 1 
 FROM sys.objects A CROSS JOIN sys.objects B 

--- [b] Create Acct table and populate with 1 million accounts
DROP TABLE IF EXISTS dbo.Acct;
CREATE TABLE dbo.Acct (
  PkID  int             not null IDENTITY(1,1),
  AcctNo    varchar(48)     not null PRIMARY KEY,
  Balance   decimal(20,10)  not null constraint DF_Balance default(0)
)
INSERT INTO dbo.Acct (AcctNo) 
 SELECT RIGHT( (REPLICATE('0',6) + CAST(number as varchar(6))), 6)
 FROM #Numbers
 ORDER BY Number

--- [c] Insert 10K transactions. Each Acct gets 2 txns
DROP TABLE IF EXISTS dbo.AcctTxns;
CREATE TABLE dbo.AcctTxns
(
  PkID      int                not null IDENTITY(1,1),
  AcctNo        varchar(48)    not null,
  TxnID     nvarchar(50)       not null,    
  Amt           decimal(20,10) not null,
  TxnStatus nvarchar(10)       not null,
  LastBalance   decimal(20,10) null
  PRIMARY KEY (AcctNo, TxnID, TxnStatus)
)
DROP TABLE IF EXISTS #Acct_Inserted_3XB9F;
CREATE TABLE #Acct_Inserted_3XB9F
(
  AcctNo        varchar(48)         not null PRIMARY KEY,   
  Balance       decimal(20,10)      null
)

declare @TxnCount int = 10000
; WITH Txns (RowNo, TxnID) AS (
   SELECT Number, '#T9-' + RIGHT(REPLICATE('0',8) + CAST(Number as varchar(8)), 8)
   FROM #Numbers WHERE Number BETWEEN 1 AND @TxnCount/2
  UNION
   SELECT Number, '#T9-' + RIGHT(REPLICATE('0',8) + CAST(Number as varchar(8)), 8)
   FROM #Numbers WHERE Number BETWEEN @TxnCount/2+1 AND @TxnCount
)
INSERT INTO dbo.AcctTxns (AcctNo, TxnID, Amt, TxnStatus)
  SELECT A.AcctNo, T.TxnID, 100, 'COMM'
  FROM dbo.Acct A JOIN Txns T ON A.PkID = T.RowNo

--- [d] Update statistics
UPDATE STATISTICS dbo.Acct;
UPDATE STATISTICS dbo.AcctTxns;

--- [e] PROBLEM HERE ...
SET STATISTICS IO, XML ON;
SELECT TxnCount=COUNT(1) 
FROM dbo.Acct A INNER JOIN dbo.AcctTxns T 
ON A.AcctNo = T.AcctNo
SET STATISTICS IO, XML OFF;

1 个答案:

答案 0 :(得分:0)

似乎您在要连接的两个列上都缺少两个非聚集索引。

CREATE NONCLUSTERED INDEX NC_AcctNo on Acct(AcctNo) INCLUDE (Balance);
CREATE NONCLUSTERED INDEX NC_AcctNo on Acctxns(AcctNo) INCLUDE (TotalAmt);

您应该对查询有更好的估计,但是如果您不使用WHERE子句过滤来自两个表的数据,则将获得索引扫描,而不是聚簇索引扫描,后者会更好如果使用上述指标,则在性能方面。

但是它们仍然会占用一些时间和资源,具体取决于您需要返回的行数。

另外,您可以查看Paul White的this answer,了解运营商估算以及该问题的其他答案。