我有两个类似的表,
CREATE TABLE [dbo].[StockPrices] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[CompanyId] INT NOT NULL,
[Date] DATETIME NOT NULL,
[Open] DECIMAL (18, 2) NOT NULL,
[Close] DECIMAL (18, 2) NOT NULL,
[Low] DECIMAL (18, 2) NOT NULL,
[High] DECIMAL (18, 2) NOT NULL,
[Volume] INT NOT NULL,
CONSTRAINT [PK_dbo.StockPrices] PRIMARY KEY NONCLUSTERED ([Id] ASC),
CONSTRAINT [FK_dbo.StockPrices_dbo.Companies_CompanyId] FOREIGN KEY ([CompanyId]) REFERENCES [dbo].[Companies] ([Id]) ON DELETE CASCADE
);
GO
CREATE CLUSTERED INDEX [IX_CompanyId] ON [dbo].[StockPrices]([CompanyId] ASC);
GO
CREATE NONCLUSTERED INDEX [IX_Date] ON [dbo].[StockPrices]([Date] ASC);
和
CREATE TABLE [dbo].[News] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[NewsProviderId] INT NOT NULL,
[CompanyId] INT NOT NULL,
[Date] DATETIME NOT NULL,
[Title] NVARCHAR (128) NOT NULL,
[Description] NVARCHAR (256) NOT NULL,
[Url] NVARCHAR (256) NOT NULL,
CONSTRAINT [PK_dbo.News] PRIMARY KEY NONCLUSTERED ([Id] ASC),
CONSTRAINT [FK_dbo.News_dbo.Companies_CompanyId] FOREIGN KEY ([CompanyId]) REFERENCES [dbo].[Companies] ([Id]) ON DELETE CASCADE
);
GO
CREATE CLUSTERED INDEX [IX_CompanyId] ON [dbo].[News]([CompanyId] ASC);
GO
CREATE NONCLUSTERED INDEX [IX_Date] ON [dbo].[News]([Date] ASC);
GO
和两个类似的查询
select *
from news
where companyid = 1
and date >= '01/01/2010'
and date <= '01/31/2010'
order by date;
select *
from stockprices
where companyid = 1
and date >= '01/01/2010'
and date <= '01/31/2010'
order by date;
我得到了两个完全不同的实际执行计划
查询1:相对于批处理:86%
SELECT (COST 0%) <- Nested Loops (Inneer Join)(Cost 0%) <- Index Seek (NonClustered) [News].[IX_Date](Cost 1%) <- Key Lookup (Clustered) [News].[IX_CompanyId](Cost 99%)
查询2:相对于批处理:14%
SELECT (Cost0%) <- Sort (Cost 33%) <- Clustered Index Scan (Clustered) [StockPrices]IX_CompanyId
我不知道为什么?你能给点建议吗?
最佳答案
第一个是按日期顺序对非覆盖聚集索引进行查找,并使用键查找来获取与 companyid = 1
匹配的行的剩余列。
第二个是对覆盖索引进行扫描,然后对过滤结果进行排序。
这是一个基于成本的决策,取决于估计匹配的表的比例以及两个索引的宽度 ( some example calculations here )。
键查找的成本很高,因为每个键查找都需要执行聚集索引查找来定位相关的页和行。这意味着非聚集索引查找所找到的每个行必须读取多个页面(与聚集索引的深度一样多的页面)。此外,为一行找到的聚集索引页很可能与下一行的页不相关,从而需要大量的随机 IO。
因此,计划切换到索引扫描之前的临界点可能只占表的很小一部分。在这种情况下,非覆盖索引可以避免排序,这一事实可能会使临界点比其他情况更高一些。
查看每个的估计行数。另请考虑,News
表包含各种字符串列,并且聚集索引页上的行数可能少于 StockPrices
表中的数值 - 因此,完整的聚集索引扫描新闻
很可能相对更昂贵,并导致更高的临界点。
关于SQL执行计划,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37773599/