为啥 SQL Server 查询优化器有时会忽略明显的聚集主键?
Posted
技术标签:
【中文标题】为啥 SQL Server 查询优化器有时会忽略明显的聚集主键?【英文标题】:Why does SQL Server query optimizer sometimes overlook obvious clustered primary key?为什么 SQL Server 查询优化器有时会忽略明显的聚集主键? 【发布时间】:2016-06-05 01:54:06 【问题描述】:我一直在摸索这个问题。
我在具有id
作为聚集整数主键 的表上运行简单的select count(id)
,SQL 优化器完全忽略其查询执行计划中的主键,支持在日期字段.... ???
实际表格:
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 Server 认为二级索引在性能方面更好用。可能是因为聚集索引需要在叶级读取数据页。 @GordonLinoff 所以不用担心? 任何非聚集索引(不包括过滤的)中的记录数将与聚集索引中的相同。然后可以使用 smallest 索引(按分配的页数)计算并得到正确的答案,花费更少的 IO。发生这种情况是因为 1) 没有WHERE
子句和 2) id
是聚集索引键(可能是主键)。 count(*)
也可以利用相同的优化。
关于 2) 的重要之处在于它 id
不可为空,因此 count(id)
与计数记录相同 (count(*)
)。
@RemusRusanu 请确认您是说 count(*) 应该比 count(id) 更有效
【参考方案1】:
1) 在我看来,这里的关键点是对于聚集表(具有聚集索引的表=主要数据结构=是存储表数据的数据结构=聚集索引是表本身)每个非聚集索引还包括聚集索引的键。这意味着
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
因此,对于这样的表,叶子页上非聚集索引的每条记录还包括聚集索引的键。这样,在每个非聚集索引和每个叶记录中,SQL Server 还存储了某种指向主数据结构的指针。
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
如果msgr
表中有大量数据,则STATISTICS IO
将显示不同的LIO
(逻辑 IO),用于 NCI 查询的 LIO 较少。
【讨论】:
我现在将检查这些STATISTICS IO
输出。您提出了一个有趣的观点,所以我们需要随着数据量的增长而改变我们的选择查询?我从来没有想过这个
没有。我的观点是,当行数很少时,no diff |就 LIO 而言,CI 和 NCI 执行计划之间只有一个小差异。【参考方案2】:
SQL Server 有一个非常好的优化器。如果它选择一个非聚集索引,那么这可能是最好的。这是我的解释。
索引将键列表和中断值存储在辅助树状数据结构中。对于非聚集索引,索引的叶子是记录标识符(指向记录的指针)。
聚集索引没有叶子。数据页本身就是叶子。因此,使用聚集索引计算记录数需要读取所有数据页。老实说,我可能认为有一种方法可以避免读取数据页读取,但读取数据页可能是必要的。
在任何情况下,非聚集索引都不需要读取原始数据页,因为所有用于计数的信息都在索引中。
【讨论】:
感谢您的解释,但我不遵循“所有用于计数的信息都在非聚集索引中”的部分。怎么样? @CharlesOkwuagwu 。 . .因为索引包含指向有效记录的指针。无需在实际数据中达到峰值即可进行计数。 对于非聚集索引,索引的叶子是记录标识符(指向记录的指针)。知道了!谢谢!【参考方案3】:SQL Server 只是遍历索引的叶子进行计数。它不必去数据。 SQL Server 已选择该索引来计算所有指针以获取计数。
【讨论】:
【参考方案4】:我在一个以 id 作为聚集整数主键的表上运行简单的 select count(id),SQL 优化器完全忽略了它的查询执行计划中的主键,有利于在日期字段上建立索引...... ???
SQL server 是一个基于成本的优化器,当它选择一个计划时,它会考虑两件事
1.查询的总成本 2.在合理的时间内选择计划。
SQL Server 查询优化器不知道的一件事是缓存中有多少页。所以它总是假设它必须从磁盘读取它们..
现在考虑到上述情况..
SQL 可能已经考虑扫描可能的最窄索引,因为 id 是主键并且它不会为空(count (id) 不包括空值)加上扫描这需要扫描所有大的索引,所以改为选择扫描另一个可能不为空的缩小索引。
【讨论】:
以上是关于为啥 SQL Server 查询优化器有时会忽略明显的聚集主键?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 SQL Server 在 equals 语句中忽略表情符号?