繁体   English   中英

索引视图更新是表扫描 1.1 亿行,用于在直方图键边界之外发现的 6 个实际行

[英]indexed view update is table scanning 110 million rows for 6 actual rows found out of histogram key bounds

在带有查询优化器修补程序的 SQL Server 2017(RTM-CU17) 上,我有一个索引视图需要花费大量时间来更新。 我不知所措,无法弄清楚为什么要对更新进行全表扫描。

索引视图有一个总和,并以高选择性(每个主键平均 5 个外行)将主键上的两个表连接到外键。 如果主表行被更新,通常会对聚合数据的外键表进行查找。 如果该行的键超出直方图的范围(高于 RANGE_HI_KEY 的最大值),则它决定使用外键对辅助表进行表扫描,即使辅助表在统计信息中具有该键值。 我在生产中得到的估计是 1.1 亿行,但实际只有 6 行……相差甚远。 由于它是升序键中的热数据,它实际上在找到它需要的 6 行之前读取了所有 1.1 亿行。

我发现已修补的 Microsoft 问题与正在发生的情况非常相似,但在这种情况下没有纠正它: https : //support.microsoft.com/en-us/help/3192154/a-non-optimal-query -plan-choice-causes-poor-performance-when-values

由于我无法共享生产代码,我可以简单地重新创建它,并且可以在此处找到计划,其中一个是有界搜索,一个是越界扫描: https : //www.brentozar.com/pastetheplan/?id =SknQqFzy8

这是我用来创建上述计划的 SQL……非常简单。 我不确定为什么要进行扫描,任何帮助将不胜感激!

--SQL Server 2017 (TRM-CU17), compatability 140, Query Optimizer Hotfixes on

CREATE TABLE dbo.tblTrans
(id INT IDENTITY(1,1) NOT NULL,
CustID INT NOT NULL,
Flag SMALLINT NOT NULL)
GO

ALTER TABLE [dbo].[tblTrans] ADD CONSTRAINT [PK_tblTrans_ID] PRIMARY KEY CLUSTERED ([ID]) WITH (FILLFACTOR=90) ON [PRIMARY]
GO

--insert random sample of data
INSERT INTO dbo.tblTrans (CustID, Flag)
VALUES (FLOOR(RAND()*1000),9)
GO 10

INSERT INTO dbo.tblTrans (CustID, Flag)
VALUES (FLOOR(RAND()*1000),3)
GO 225

INSERT INTO dbo.tblTrans (CustID, Flag)
VALUES (FLOOR(RAND()*1000),7)
GO 25

INSERT INTO dbo.tblTrans (CustID, Flag)
VALUES (FLOOR(RAND()*1000),4)
GO 185

INSERT INTO dbo.tblTrans (CustID, Flag)
VALUES (FLOOR(RAND()*1000),5)
GO 150

INSERT INTO dbo.tblTrans (CustID, Flag)
VALUES (FLOOR(RAND()*1000),8)
GO 15

INSERT INTO dbo.tblTrans (CustID, Flag)
VALUES (FLOOR(RAND()*1000),2)
GO 110

CREATE TABLE dbo.tblTrans_Detail
(id INT IDENTITY(1,1) NOT NULL,
transid INT NOT NULL,
Amount MONEY NOT NULL)
GO

ALTER TABLE [dbo].[tblTrans_Detail] ADD CONSTRAINT [PK_tblTrans_Detail_ID] PRIMARY KEY CLUSTERED ([ID]) WITH (FILLFACTOR=90) ON [PRIMARY]
GO

--insert random data into detail table
INSERT INTO dbo.tblTrans_Detail (transid, Amount)
SELECT id, CustID+10 AS amount
FROM dbo.tblTrans

INSERT INTO dbo.tblTrans_Detail (transid, Amount)
SELECT id, CustID+12 AS amount
FROM dbo.tblTrans

INSERT INTO dbo.tblTrans_Detail (transid, Amount)
SELECT id, CustID+13 AS amount
FROM dbo.tblTrans

GO

CREATE VIEW [dbo].[ivw_Get_Trans]
WITH SCHEMABINDING
AS
SELECT
dbo.tblTrans.CustID ,
SUM(dbo.tblTrans_Detail.Amount) AS Amount,
COUNT_BIG(*) AS CBCount
FROM dbo.tblTrans
INNER JOIN dbo.tblTrans_Detail
ON tblTrans.id = tblTrans_Detail.TransID
WHERE ( dbo.tblTrans.Flag = 2 )
GROUP BY dbo.tblTrans.CustID
GO

CREATE UNIQUE CLUSTERED INDEX [idx_vw_Trans] ON [dbo].[ivw_Get_Trans] (
[CustID]
) WITH (FILLFACTOR=90, STATISTICS_NORECOMPUTE=OFF) ON [PRIMARY]
GO

--DBCC SHOW_STATISTICS ('dbo.tbltrans',pk_tbltrans_id)
--MAX RANGE_HI_KEY is 720 (also max id from table), key is selective

INSERT INTO dbo.tbltrans (CustID, Flag)
OUTPUT Inserted.id
VALUES (100, 5)
GO

--ID 721 is inserted
INSERT INTO dbo.tbltrans_detail (transid, Amount)
VALUES (721,13.00)
GO

--new ID is in stats of detail table but not stats for trans table
CREATE INDEX IX_tblTrans_Detail_TransID ON dbo.tbltrans_detail (TransID, Amount)
GO

--DBCC SHOW_STATISTICS ('dbo.tbltrans_detail',ix_tbltrans_detail_Transid)

--DBCC FREEPROCCACHE

--set showplan on

BEGIN TRANSACTION

UPDATE dbo.tblTrans
SET Flag = 2
WHERE ID = 720 --seek (highest value in histogram, tblTrans)
--WHERE ID = 721 --scan (out of bounds in histogram, tblTrans)

UPDATE dbo.tblTrans
SET Flag = 2
--WHERE ID = 720 --seek (highest value in histogram, tblTrans)
WHERE ID = 721 --scan (out of bounds in histogram, tblTrans)

ROLLBACK TRANSACTION

--DROP view dbo.ivw_Get_Trans
--drop table dbo.tblTrans
--drop table dbo.tblTrans_Detail

当像id INT IDENTITY(1,1)这样不断增加的列是您的Clustered Index您就不需要打扰FillFacor 。让它成为默认的Fillfactor 0 或 100。

这将减少页数,因此优化器将不得不读取更少的页数。

不时重建统计,包含外界值721

Update Statistics tbltrans  with FullScan
GO

外键约束未定义。

 ALTER TABLE dbo.tblTrans_Detail WITH CHECK
     ADD CONSTRAINT FK_tbltrans_Id FOREIGN KEY (TransId)
     REFERENCES dbo.tbltrans(id) 

 ALTER TABLE dbo.tblTrans_Detail with check check CONSTRAINT FK_tbltrans_Id

确保它是可信的,

SELECT name, is_disabled, is_not_trusted
FROM sys.foreign_keys
WHERE name = 'FK_tbltrans_Id'

值得信赖的 FK 帮助优化器制定更好的执行计划。

除了table scan还有几个原因。 Optimizer更经常地快速制定足够好的计划,这是具有成本效益的。

所以所有表扫描都不是很糟糕,如果您使用提示强制 InDex 查找,则查询成本可能会增加,这是不可取的。

您必须注意到,每当 tblTrans 及其详细信息中有插入时,索引视图也会在插入的同一查询计划中更新。这意味着由于视图索引的更新,插入成本增加。

因此,如果 tblTrans 及其详细表是高度事务性的,那么您应该避免使用索引视图。

新 ID 在明细表的统计信息中,但不在 trans 表的统计信息中

两个表的统计信息都应该更新。

**Edit 1**

根据你现在的情况

Filfactor点即使没有鞋子任何改进也可以。

Trusted FK 在这种情况下没有帮助,因为当前的问题与 View 有关。

我能够重现这个问题。我不能把执行计划放在这里。

我用 700000 条记录填充了 tbltran,并且 tblTrans_Detail 具有每个 FK 7 条记录,即 700000x7 条记录。

CREATE TABLE dbo.tblTrans
(id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
CustID INT NOT NULL,
Flag SMALLINT NOT NULL)
GO

declare @i int=1
declare @Flag int=1
while (@i<70000)
begin

if(@i%2=0)
set @Flag=2
else if(@i%3=0)
set @Flag=3
else if(@i%7=0)
set @Flag=7
else if(@i%5=0)
set @Flag=5

INSERT INTO dbo.tblTrans (CustID, Flag)
VALUES (FLOOR(RAND()*1000),@Flag)

set @i=@i+1
end


CREATE TABLE dbo.tblTrans_Detail
(id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
transid INT NOT NULL,
Amount MONEY NOT NULL)
GO

    INSERT INTO dbo.tblTrans_Detail with (tablock)(transid, Amount)
    SELECT id, CustID+10 AS amount
    FROM dbo.tblTrans,tblnumber
    where number<=7

where `tblnumber` is number table ,you can create `#temp` table containing 1 to 20 rows maximum.

目前这两个表之间没有定义关系,或者tbltrans_detailtbltrans没有索引。

观不打扰,观即如是。

所以当我运行这个查询时,我也得到了 tblTransdetail Table/Index (IX_tblTrans_Detail_TransID) Scan (同样的问题)

--dbcc freeproccache
BEGIN TRANSACTION

UPDATE dbo.tblTrans
SET Flag = 2
--WHERE ID = 720 --seek (highest value in histogram, tblTrans)
WHERE ID = 70000  --1--scan (out of bounds in histogram, tblTrans)

ROLLBACK TRANSACTION

它正在读取 tblTrans_Detail 的所有行以获取 7 行。

当我像这样创建索引时,

CREATE NONCLUSTERED INDEX [IX_tblTrans_Detail] ON [dbo].[tblTrans_Detail]
(
    [transid] ASC
)include(Amount)
GO 

我在IX_tblTrans_Detail_TransID

Estimated number of rows=Actial number of Rows=7

真正的技巧:在两个表中以特定方式排列索引键 Transid。表 tblTrans_Detail 中的早期 Transid 分散在这里和那里,因此它正在扫描整个表。

现在,在两个表中的tblTrans_Detail中创建Non Clustered index索引键Transid后,它们以特定的方式排列。因此优化器可以快速找到所需的行数。

所以可能是110 millions row你仍然得到索引扫描。所以你可能应该放弃视图的想法,因为每次插入触发时视图索引都会更新。

Update Statistics and Rebuild Index.

或者,

  1. 禁用视图
  2. 在 tbl trans 上创建过滤索引

    在 TblTrans (Custid) 上创建非聚集索引 nci_custid_tblTrans,其中 Flag=2 Go

重要笔记 :

优化器通常以具有成本效益的方式快速制定足够好的计划。

您可以查看您的两个 XML 计划,在这两个计划中您都可以找到

StatementOptmEarlyAbortReason="GoodEnoughPlanFound"

因此,根据执行计划,实际上没有任何问题。

经过进一步研究,我发现这也发生在级联删除中。 启用跟踪标志 2363 显示“计算器失败。重新规划”。 然后提出“选择性:1”(子表中的所有内容)。 入站数据将搜索,任何高于最高直方图键值范围的键值都将被扫描。

我已通过以下链接将此作为错误提交给 MS: https : //feedback.azure.com/forums/908035-sql-server/suggestions/39359128-cascade-deletes-and-indexed-view-updates-causing- F

更新:这在 SQL Server 2017 CU22 中得到了修补: 索引视图更新是表扫描 1.1 亿行,用于在直方图键边界之外发现的 6 个实际行

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM