![](/img/trans.png)
[英]Will updating any field in base table cause updating will all rows of the indexed view rows?
[英]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_detail
和tbltrans
没有索引。
观不打扰,观即如是。
所以当我运行这个查询时,我也得到了 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.
或者,
在 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.