为什么 SQL 服务器查询优化器有时会忽略明显的聚簇主键?

Why does SQL Server query optimizer sometimes overlook obvious clustered primary key?

这个我一直摸不着头脑。

我 运行 简单 select count(id) 在 table 上 id 作为 集群整数主键 和 SQL 优化器完全忽略其查询执行计划中的主键,支持日期字段上的索引.... ???

实际 table:

CREATE TABLE [dbo].[msgr](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [dt] [datetime2](3) NOT NULL CONSTRAINT [DF_msgr_dt]  DEFAULT (sysdatetime()),
    [uid] [int] NOT NULL,
    [msg] [varchar](7000) NOT NULL CONSTRAINT [DF_msgr_msg]  DEFAULT (''),
    [type] [tinyint] NOT NULL,
    [cid] [int] NOT NULL CONSTRAINT [DF_msgr_cid]  DEFAULT ((0)),
    [via] [tinyint] NOT NULL,
    [msg_id] [bigint] NOT NULL,
 CONSTRAINT [PK_msgr] PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

请问这是什么原因?

SQL 服务器只是遍历索引的叶子进行计数。它不必去数据。 SQL 服务器已选择该索引来计算所有指针以获得计数。

SQL服务器有一个很好的优化器。如果它选择非聚集索引,那么这可能最适合此目的。这是我的解释。

索引在辅助树状数据结构中存储键和中断值的列表。对于非聚集索引,索引的叶子是记录标识符(指向记录的指针)。

聚簇索引没有叶子。数据页本身就是叶子。因此,使用聚集索引计算记录数需要读取所有数据页。老实说,我可能认为有一种方法可以避免读取数据页,但可能需要读取数据页。

无论如何,非聚集索引不需要读取原始数据页,因为所有用于计数的信息都在索引中。

I run as simple select count(id) on a table with id as clustered integer primary key and the SQL Optimizer totally ignores the primary key in it's query execution plan, in favor of an index on a date field.... ???

SQL服务器是一个基于成本的优化器,无论何时选择一个计划,它都会考虑两件事

1.Total cost of the query
2.Choosing the plan in a reasonable amount of time.

一件事 SQL 服务器查询优化器不知道有多少页 cache.So 它总是假设它必须从磁盘读取它们..

现在考虑以上几点..

SQL might have thought to scan the narrowest possible index since id is a primary key and this wont be null (count (id) excludes null values)plus scanning this requires to scan all of the index which is big,so instead choose to scan another index narrow one possibly which is not null.

1) 在我看来,这里的关键点是对于 clustered tables (tables 其中有一个聚集索引 = main数据结构=是存储table数据的数据结构=聚集索引是table本身)每个非聚集索引也包括聚集索引的键.这意味着

CREATE [UNIQUE] NONCLUSTERED INDEX bla 
ON [dbo].[msgr] (uid) 

基本相同
CREATE [UNIQUE] NONCLUSTERED INDEX bla 
ON [dbo].[msgr] (uid) 
INCLUDE (id) -- id = key of clustered index

因此,对于这样的 tables,叶页上非聚簇索引的每条记录还包括聚簇索引的键。这样,在每个非聚集索引和每个叶记录中 SQL 服务器还存储某种指向主数据结构的指针。

2) 这意味着 SELECT COUNT(id) FROM dbo.msgr 可以使用 CI 执行,但也可以使用 NCI 执行,因为两个索引都包含 id (聚簇索引的键)列.

作为本主题的次要注释,因为 IDENTITY 属性(对于 id 列)表示强制列(NOT NULL),COUNT(id)COUNT(*) 相同。此外,这意味着 COUNT(msg_id)(也是必需的/NOT NULL)列与 COUNT(*) 相同。因此,SELECT COUNT(msg_id) FROM dbo.msgr 的执行计划很可能会使用相同的 NCI(例如 bla)。

3) 非聚集索引的大小小于聚集索引。这也意味着更少的 IO => 从性能的角度来看,使用 NCI 比 CI.

更好

我会做以下简单测试:

SET STATISTICS IO ON;
GO

SELECT COUNT(id)
FROM dbo.[dbo].[msgr] WITH(INDEX=[bla]) -- It forces usage of NCI
GO

SELECT COUNT(id)
FROM dbo.[dbo].[msgr] WITH(INDEX=[PK_msgr]) -- It forces usage of CI
GO

SET STATISTICS IO OFF;
GO

如果msgrtable内有很多数据那么STATISTICS IO会显示不同的LIO(逻辑IO),少LIO对于 NCI 查询.