简体   繁体   中英

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

On SQL Server 2017(RTM-CU17) with query optimizer hot fixes on I have an indexed view that is taking a considerable amount of time to update. I am at a loss and cannot figure out why a full table scan is occurring for the update.

The indexed view has a sum and is joining two tables on primary key to foreign key with high selectivity (avg 5 foreign rows per primary key). If the primary table row is updated a seek typically occurs to the foreign key table for the aggregate data. If the row has a key that is out of bounds of the histogram (higher than the max value of the RANGE_HI_KEY) it decides to table scan the secondary table with the foreign key, even if the secondary table has the key value in the statistics. What I get in production is an estimated 110 million rows but only 6 actual rows...pretty far off. Since it is hot data in an ascending key it actually reads all 110 million rows before it finds the 6 it needs.

I found a Microsoft issue that was patched that is very similar to what is happening but does not correct it in this situation: https://support.microsoft.com/en-us/help/3192154/a-non-optimal-query-plan-choice-causes-poor-performance-when-values

As I cant share production code I was able to simply recreate it and the plans can be found here, one with an in bound seek, one with an out of bounds scan: https://www.brentozar.com/pastetheplan/?id=SknQqFzy8

Here is the SQL I used to create the plans above...very simple. I am not sure why a scan is happening, any help would be very appreciated!

--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

When column like id INT IDENTITY(1,1) which is ever increasing is your Clustered Index then you do no need to disturb FillFacor .Let it be default Fillfactor 0 or 100.

This will reduce no of pages,so optimizer will have to read less number of pages.

Rebuild Statistics from time to time,to incude outside bound value 721

Update Statistics tbltrans  with FullScan
GO

Foreign key constraint is not define.

 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

Make sure that it is trusted,

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

Trusted FK help Optimizer make better execution plan.

Beside there are several reason for table scan . Optimizer more often make good enough plan quickly which is cost effective.

So all table scan are not really bad,If you force InDex seek using hint then cost of query may increase which is not desirable.

You must have notice that whenever there is insert in tblTrans and its detail then Index view is also updated in same query plan of Insert.That means Insert cost increases because of Updation of view index.

So if tblTrans and its detail tables are highly transactional then you should avoid Index View.

new ID is in stats of detail table but not stats for trans table

Statistics of both table should updated.

**Edit 1**

According to your current situation

Filfactor point is ok even if did not shoe any improvement.

Trusted FK didn't help in this situation because current problem is related with View.

I was able to reproduce the problem.I cannot put execution plan here.

I populated tbltran with 700000 records,and tblTrans_Detail having each FK 7 recors ie 700000x7 records.

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.

Currently there is no define relation between these 2 tables or there is no index in tbltrans_detail and tbltrans .

View is not disturb, View is as it is.

So when I run this query, I too get tblTransdetail Table/Index (IX_tblTrans_Detail_TransID) Scan (same problem)

--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

It was reading all rows of tblTrans_Detail to fetch 7 rows.

When I create index like this,

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

I get Index seek on IX_tblTrans_Detail_TransID and

Estimated number of rows=Actial number of Rows=7

Real Trick : To arrange index key Transid in both table in particular manner.Earlier Transid in table tblTrans_Detail were scattered here and there so it was scanning complete table.

Now After creating Non Clustered index index key Transid in tblTrans_Detail in both table are arrange in partuclar manner.So Optimizer quickly find require number of rows.

So may be 110 millions row u still get Index Scan.So may be you should drop the idea of view because View Index is getting updated every time when insert fire.

Update Statistics and Rebuild Index.

Alternatively,

  1. Disable view
  2. Create Filtered Index on tbl trans

    Create NonClustered Index nci_custid_tblTrans on TblTrans (Custid) where Flag=2 Go

Important Notes :

Optimizer most often make good enough plan qucikly in cost effective manner.

You can view both XML Plan of yours, in both plan you can find

StatementOptmEarlyAbortReason="GoodEnoughPlanFound"

So according to Execution Plan there is not really any problem.

After additional research I have discovered this is also occurring on cascade deletes as well. Enabling trace flag 2363 shows "Calculator failed. Replanning." and then coming up with "Selectivity: 1" (everything in child table). In-bounds data will seek, any key value higher than highest histogram key range will scan.

I have submitted this to MS as a bug through the following link: https://feedback.azure.com/forums/908035-sql-server/suggestions/39359128-cascade-deletes-and-indexed-view-updates-causing-f

Update: this was patched in SQL Server 2017 CU22: indexed view update is table scanning 110 million rows for 6 actual rows found out of histogram key bounds

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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