简介和背景
我必须优化一个简单的查询(例如下面的例子)。在多次重写之后,我发现同一个索引操作的估计行数根据查询的编写方式而有所不同。
最初查询执行了聚簇索引扫描,因为生产中的表包含表格非常大的二进制列(大约100 GB),并且全表扫描需要太多时间来执行。
问题
为什么估计的行数在同一个索引操作上有所不同(例子会显示)?优化器在这做什么?
示例数据库 - 我正在使用SQL Server 2008 R2
我尝试创建一个非常简单的生产表版本来显示行为。
-- CREATE THE SAMPLE TABLES
----------------------------
CREATE TABLE dbo.MasterTable(
MasterId smallint NOT NULL,
Name varchar(5) NOT NULL,
CONSTRAINT PK_MasterTable PRIMARY KEY CLUSTERED (MasterId ASC)
) ON [PRIMARY]
GO
CREATE TABLE dbo.DetailTable(
DetailId bigint IDENTITY(1,1) NOT NULL,
MasterId smallint NOT NULL,
Name nvarchar(50) NOT NULL,
CreateDate datetime NOT NULL,
CONSTRAINT PK_DetailTable PRIMARY KEY CLUSTERED (DetailId ASC)
) ON [PRIMARY]
GO
ALTER TABLE dbo.DetailTable
ADD CONSTRAINT FK1
FOREIGN KEY(MasterId) REFERENCES dbo.MasterTable (MasterId)
GO
CREATE NONCLUSTERED INDEX IX_DetailTable
ON dbo.DetailTable( MasterId ASC, Name ASC )
GO
-- INSERT SOME SAMPLE DATA
----------------------------
SET NOCOUNT ON
GO
-- These are some Codes. In our system we always use these codes to search for "types" of data.
INSERT INTO dbo.MasterTable (MasterId, Name)
VALUES (1, 'N1'), (2, 'N2'), (3, 'N3'), (4, 'N4'), (5, 'N5'), (6, 'N6'), (7, 'N7'), (8, 'N8')
GO
-- ADD ROWS TO THE DETAIL TABLE
-- Takes about 1 minute to run
-- Don't care about the logic, it's just to get a distribution similar to production system
----------------------------
declare @x int = 1
DECLARE @MasterID INT
while (@x <= 400000)
begin
SET @MasterID = ABS(CHECKSUM(NEWID())) % 8 + 1
INSERT INTO dbo.DetailTable(MasterId,Name,CreateDate)
VALUES(
CASE
WHEN @MasterID IN (1, 3, 4) AND @x % 20 != 0 THEN 2
WHEN @MasterID IN (5, 6) AND @x % 20 != 0 THEN 7
WHEN @MasterID = 8 AND @x % 100 != 0 THEN 7
ELSE @MasterID
END,
NEWID(),
DATEADD(DAY, - ABS(CHECKSUM(NEWID())) % 1000, GETDATE())
)
SET @x = @x + 1
end
go
-- DO THE INDEX AND STATISTIC MAINTENANCE
----------------------------
alter index all on dbo.DetailTable reorganize
alter index all on dbo.MasterTable reorganize
update statistics dbo.DetailTable WITH FULLSCAN
update statistics dbo.MasterTable WITH FULLSCAN
go
准备完成后,让我们从查询开始
让我们先看看统计数据,看看RANGE_HI_KEY=8
,有489个EQ_ROWS
-- CHECK THE STATISTICS
----------------------------
dbcc show_statistics ('dbo.DetailTable', IX_DetailTable)
GO
现在我们进行查询。第一个是我必须优化的原始查询。 请在执行时激活当前的执行计划。 看看操作“索引搜索(非聚集)[DetailTable]。[IX_DetailTable]”
-- ORIGINAL QUERY
----------------------------
SELECT d.DetailId
FROM dbo.DetailTable d
INNER JOIN dbo.MasterTable m ON d.MasterId = m.MasterId
WHERE m.Name = 'N8'
AND d.CreateDate > '20150312 11:00:00'
GO
-- FORCESEEK
----------------------------
SELECT d.DetailId
FROM dbo.DetailTable d WITH (FORCESEEK)
INNER JOIN dbo.MasterTable m ON d.MasterId = m.MasterId
WHERE m.Name = 'N8'
AND d.CreateDate > '20150312 11:00:00'
GO
-- Actual: 489, Estimated 50.000
-- TABLE VARIABLE
----------------------------
DECLARE @MasterId AS TABLE( MasterId SMALLINT )
INSERT INTO @MasterId (MasterId)
SELECT MasterID FROM dbo.MasterTable WHERE Name = 'N8'
SELECT d.DetailId
FROM dbo.DetailTable d WITH (FORCESEEK)
INNER JOIN @MasterId m ON d.MasterId = m.MasterId
WHERE d.CreateDate > '20150312 11:00:00'
GO
-- Actual: 489, Estimated 40.000
-- TEMP TABLE
----------------------------
CREATE TABLE #MasterId( MasterId SMALLINT )
INSERT INTO #MasterId (MasterId)
SELECT MasterID FROM dbo.MasterTable WHERE Name = 'N8'
SELECT d.DetailId
FROM dbo.DetailTable d --WITH (FORCESEEK)
INNER JOIN #MasterId m ON d.MasterId = m.MasterId
WHERE d.CreateDate > '20150312 11:00:00'
-- Actual 489, Estimated 489
DROP TABLE #MasterId
GO
分析和最终问题
请查看操作“索引搜索(非聚集)[DetailTable]。[IX_DetailTable]”
上面脚本中的注释显示了我获得的估计值和实际行数的值。
在我们的生产环境中,此表有3300万行,上述查询中的估计行数在300万到1600万之间不等。
总结:
当建立DetailTable和MasterTable之间的连接时,估计的rowcount为12.5%(主表中有8个值,有意义,有点......)
当DetailTable与表变量之间建立连接时,估计的行数为10%
当DetailTable与临时表之间建立连接时,估计的行数与实际行数完全相同
问题是为什么这些值会有所不同?
统计数据是最新的,估算应该很容易。
我只是想了解这一点。
答案 0 :(得分:0)
没有人回答我会尝试回答:
请不要强迫优化人员关注你
(1)关于原始查询的说明:
SELECT d.DetailId
FROM dbo.DetailTable d
INNER JOIN dbo.MasterTable m ON d.MasterId = m.MasterId
WHERE m.Name = 'N8'
AND d.CreateDate > '20150312 11:00:00'
为什么这个查询很慢?
此查询速度很慢,因为您的索引未覆盖此查询, 两个查询都使用索引扫描而不是加入&#34;哈希联接&#34;:
为什么扫描整行是否可以使用? 因为Master表上的索引位于MasterId列上,而不是列Name上。
为什么要扫描详细信息表的整行?因为这里也有索引
(DETAILID)&#34; CLUSTERED&#34; AND(MasterId ASC,名称ASC)&#34; NON CLUSTERED&#34;
不在Createdate列上。
拥有一个NONCLUSTERED索引将有助于此查询对此特定查询的ON列(CREATEDATE,MasterId)。
如果您的主表格很大,您可以在(名称)列上创建NONCLUSTERED索引。
(2)FORCESEEK说明:
SELECT d.DetailId
FROM dbo.DetailTable d WITH (FORCESEEK)
INNER JOIN dbo.MasterTable m ON d.MasterId = m.MasterId
WHERE m.Name = 'N8'
AND d.CreateDate > '20150312 11:00:00'
GO
为什么优化器估计有50,000行?
在这里你加入了d.MasterId = m.MasterId列,你正在强制优化器选择seek on Detail表,所以 使用INDEX IX_DetailTable()的optizer使用LOOP连接加入您的Mastertable。
由于Optimizer选择循环连接将MAster表的所有行(实际上是ONE)连接到Detail表 所以它将从主表中选择一个键,然后查找整个索引,然后将匹配值传递给进一步的迭代器。
所以优化器选择每个值的行数平均值。 列40000表基数(行)中的8个唯一值 40000/8估计50,000行(足够公平)。
以下是您的查询:
DECLARE @MasterId AS TABLE( MasterId SMALLINT )
INSERT INTO @MasterId (MasterId)
SELECT MasterID FROM dbo.MasterTable WHERE Name = 'N8'
SELECT d.DetailId
FROM dbo.DetailTable d WITH (FORCESEEK)
INNER JOIN @MasterId m ON d.MasterId = m.MasterId
WHERE d.CreateDate > '20150312 11:00:00'
GO
Statatictic没有维护表变量,所以optimzer没有idia多少行(所以它估计1行)它与gonaa处理以产生一个好的计划, 这里估计的行数是1,实际的行1也是如此!!
但是如何优化估计&#34; 40.000&#34; ROWS
就我个人而言,我从来没有检查过这个,因为这个问题我做了血清测试,但是没有idia如何计算估计行的优化程序,所以如果有人上来让我们感到高兴就会很棒。
(4) - TEMP TABLE
您的查询
CREATE TABLE #MasterId( MasterId SMALLINT )
INSERT INTO #MasterId (MasterId)
SELECT MasterID FROM dbo.MasterTable WHERE Name = 'N8'
SELECT d.DetailId
FROM dbo.DetailTable d --WITH (FORCESEEK)
INNER JOIN #MasterId m ON d.MasterId = m.MasterId
WHERE d.CreateDate > '20150312 11:00:00'
-- Actual 489, Estimated 489
DROP TABLE #MasterId
这里以及优化器选择与表变量中选择相同的查询计划,但差异是 统计数据确实维护临时表,所以这里查询优化器有一个公平的idia它实际要加入的行。 &#34; N8&#34; key有8个,dbo中的8个估计行.DetailTable是489。