Sql server 同一索引操作上的不同估计行?

Sql server 同一索引操作上的不同估计行?,sql-server,sql-server-2008-r2,histogram,optimization,sql-server-performance,Sql Server,Sql Server 2008 R2,Histogram,Optimization,Sql Server Performance,介绍和背景 我必须优化一个简单的查询(下面的例子)。在多次重写之后,我发现同一索引操作上的估计行数因编写查询的方式而异 最初,查询执行聚集索引扫描,因为生产中的表包含一个二进制列,所以该表相当大(约100GB),并且执行完整表扫描需要花费太多时间 问题 为什么在同一索引操作上估计的行数不同(示例将显示)?优化器在这里做什么 示例数据库-我正在使用SQL Server 2008 R2 我试图创建一个非常简单的生产表版本来显示这种行为 -- CREATE THE SAMPLE TABLES ----

介绍和背景

我必须优化一个简单的查询(下面的例子)。在多次重写之后,我发现同一索引操作上的估计行数因编写查询的方式而异

最初,查询执行聚集索引扫描,因为生产中的表包含一个二进制列,所以该表相当大(约100GB),并且执行完整表扫描需要花费太多时间

问题

为什么在同一索引操作上估计的行数不同(示例将显示)?优化器在这里做什么

示例数据库-我正在使用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
现在我们进行查询。第一个是我必须优化的原始查询。 执行时请激活当前执行计划。 查看操作“索引搜索(非聚集)[DetailTable].[IX_DetailTable]”

分析和最终问题

请查看操作“索引搜索(非聚集)[DetailTable].[IX_DetailTable]”

上面脚本中的注释显示了我获得的估计行数和实际行数的值

在我们的生产环境中,这个表有3300万行,上面查询中的估计行数从300万到1600万不等

总结如下:

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'
  • 当在DetailTable和MasterTable之间进行连接时,估计的行数是12,5%(MasterTable中有8个值,这很有意义,有点…)

  • 在DetailTable和table变量之间进行联接时,估计的行数为10%

  • 在DetailTable和temp表之间进行联接时,估计的行数与实际行数完全相同

  • 问题是为什么这些值不同

    统计数据是最新的,估计应该很容易


    我只是想理解这一点。

    由于没有人回答,我将尝试给出答案:

    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'
    
    请不要强迫优化器跟随您

    (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'
    
    为什么这个查询很慢

    此查询速度较慢,因为索引未覆盖此查询, 两个查询都使用索引扫描,然后使用“哈希连接”进行连接:

    为什么要扫描整行的主表? 因为主表上的索引位于列MasterId上,而不是列名称上

    为什么要扫描整行的Detailtable?因为这里的索引也是打开的 (DETAILID)“集群”和(MasterId ASC,Name ASC)“非集群”
    不在Createdate列上

    拥有一个非聚集索引将有助于此特定查询的列(CREATEDATE,MasterId)查询

    若主表也是巨大的,那个么可以在(Name)列上创建非聚集索引

    (2)FORCESEEK的说明:

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

    为什么优化器估计50000行

    在这里,您加入到d.MasterId=m.MasterId列,并强制优化器选择在细节表上搜索,所以 optizer使用索引IX_DetailTable()使用循环联接来联接主表

    因为优化器选择使用循环联接将主表的所有行(实际上是一行)联接到明细表 所以它将从主表中选择一个键,然后搜索整个索引,然后将匹配值传递给下一个迭代器

    所以优化器选择每个值的行数平均值。 列40000表基数(行)中的8个唯一值 40000/8估计为50000行(足够公平)

    (3) --表变量 以下是您的疑问:

    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
    

    Statictic不维护表变量,因此Optimizer没有idia,它需要处理多少行(因此它估计1行)才能生成一个好的计划, 这里还有估计好的行是1,实际的行1也表示祝贺

    但优化器是如何估计“40000”行的呢

    就我个人而言,我从来没有检查过这一点,因为这个问题,我做了servels测试,但没有idia Optimizer如何计算估计行,所以如果有人来告诉我们这将是非常棒的

    (4)——临时表

    你的问题

    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,它实际上要加入哪一行。
    “N8”键有8行,dbo中估计有8行。DetailTable是489。

    hey@CTi措辞很好的问题我不知道为什么没有对你投赞成票。我想你也可以重新标记这个问题