Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/sql-server/21.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql server 索引视图更新是表扫描1.1亿行,查找直方图键边界之外的6个实际行_Sql Server_Histogram_Database Administration_Indexed View_Full Table Scan - Fatal编程技术网

Sql server 索引视图更新是表扫描1.1亿行,查找直方图键边界之外的6个实际行

Sql server 索引视图更新是表扫描1.1亿行,查找直方图键边界之外的6个实际行,sql-server,histogram,database-administration,indexed-view,full-table-scan,Sql Server,Histogram,Database Administration,Indexed View,Full Table Scan,在SQL Server 2017(RTM-CU17)上安装了查询优化器热修复程序,我有一个索引视图,需要花费大量时间进行更新。我不知所措,无法理解为什么要对更新进行完整的表扫描 索引视图有一个和,并以高选择性(每个主键平均5个外键行)将主键上的两个表连接到外键。如果主表行被更新,则聚合数据的外键表通常会出现seek。如果该行有一个键超出了直方图的界限(高于RANGE_HI_键的最大值),则它会决定使用外键对次表进行表扫描,即使次表的统计信息中有该键值。我在生产中得到的估计是1.1亿行,但实际只有

在SQL Server 2017(RTM-CU17)上安装了查询优化器热修复程序,我有一个索引视图,需要花费大量时间进行更新。我不知所措,无法理解为什么要对更新进行完整的表扫描

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

我发现一个Microsoft问题已修补,与正在发生的情况非常相似,但在这种情况下无法纠正:

由于我无法共享生产代码,因此我可以简单地重新创建它,并且可以在此处找到计划,一个带有内界搜索,一个带有外界扫描:

下面是我用来创建上述计划的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)
这样不断增加的列是您的
聚集索引时,您不需要干扰
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帮助优化器制定更好的执行计划

除此之外,
表格扫描还有几个原因。
优化器
更经常地快速制定足够好的计划,这是一种经济高效的方法

所以所有的表扫描都不是很糟糕,若您使用提示强制索引查找,那个么查询的成本可能会增加,这是不可取的

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

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

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

应更新两个表的统计数据

**Edit 1**
根据你目前的情况

即使没有鞋的任何改善因素点也可以

受信任的FK在这种情况下没有帮助,因为当前问题与视图有关

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

我用700000条记录填充了tbltran,tblTrans_详细信息包含每个FK 7记录,即70000X7条记录

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_细节的所有行,以获取7行

当我创建这样的索引时

CREATE NONCLUSTERED INDEX [IX_tblTrans_Detail] ON [dbo].[tblTrans_Detail]
(
    [transid] ASC
)include(Amount)
GO 
我在
IX_tblTrans_Detail_TransID
上获得索引seek,并且

Estimated number of rows=Actial number of Rows=7
真正的诀窍:在两个表中以特定的方式排列索引键Transid。表tblTrans_细节中的早期Transid分散在这里和那里,所以它是在扫描整个表

现在,在
tblTrans\u Detail
中创建了
非聚集索引后,两个表中的索引键
Transid
以部分顺序排列。因此优化器可以快速找到所需的行数

所以可能是
1.1亿行
u仍然得到索引扫描。也可能是你应该放弃查看的想法,因为每次插入fire时视图索引都会更新

Update Statistics and Rebuild Index.
或者

  • 禁用视图
  • 在tbl trans上创建筛选索引

    在tblTrans(custid)上创建非聚集索引nci_custid_tblTrans 其中Flag=2 去

  • 重要注意事项:

    优化器通常以经济高效的方式做出足够好的计划

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

    StatementOptmEarlyAbortReason="GoodEnoughPlanFound"
    

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

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

    我已通过以下链接将此作为错误提交给MS:


    更新:这是在SQL Server 2017中修补的CU22:

    我建议将此帖子改为以下帖子,如果您在复制中包含FK,它是否仍然存在问题?在SQL Server 2016(SP2-CU11)(开发人员)上尝试了您的脚本,并且两个计划都在tblTrans\u细节上使用了嵌套循环连接索引seek。第二个(越界)查询在使用OPTION(QUERYTRACEON 4199或USE HINT('ENABLE_query_OPTIMIZER_HOTFIXES')执行时,具有与您所发布的相同的计划(合并联接和表扫描)。我很好奇,在问题的示例代码中添加FK是否会停止表扫描。感谢您提供的关于填充因子的信息—我甚至没有注意到它。将其更改为0并添加受信任的FK仍然不会更改扫描与搜索行为。唯一改变它的是用全扫描更新统计数据,这样所有的热数据都在范围内。我确实发现了一个有趣的“bug”报告,它非常相似@BenH,你能说出在1.1亿条记录中,
    dbo.tblTrans.Flag=2
    有多少条记录吗?@KumarHarsh,有Flag的记录不到300万条2@BenH,查询是否花费了太多的时间/合理的时间或相同的时间。请阅读我编辑的。
    StatementOptmEarlyAbortReason="GoodEnoughPlanFound"