AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / dba / 问题 / 286492
Accepted
Jez
Jez
Asked: 2021-03-06 03:51:25 +0800 CST2021-03-06 03:51:25 +0800 CST 2021-03-06 03:51:25 +0800 CST

为什么 SQL Server 在此 SELECT - OFFSET - FETCH 中执行大量不必要的聚集键查找?

  • 772

我有下表:

CREATE TABLE [dbo].[MP_Notification_Audit](
    [id] [bigint] IDENTITY(1,1) NOT NULL,
    [type] [int] NOT NULL,
    [source_user_id] [bigint] NOT NULL,
    [target_user_id] [bigint] NOT NULL,
    [discussion_id] [bigint] NULL,
    [discussion_comment_id] [bigint] NULL,
    [discussion_media_id] [bigint] NULL,
    [patient_id] [bigint] NULL,
    [task_id] [bigint] NULL,
    [date_created] [datetimeoffset](7) NOT NULL,
    [clicked] [bit] NULL,
    [date_clicked] [datetimeoffset](7) NULL,
    [title] [nvarchar](max) NULL,
    [body] [nvarchar](max) NULL,
 CONSTRAINT [PK_MP_Notification_Audit] PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

ALTER TABLE [dbo].[MP_Notification_Audit] ADD  CONSTRAINT [DF_MP_Notification_Audit_date_created]  DEFAULT (sysdatetimeoffset()) FOR [date_created]
GO

CREATE NONCLUSTERED INDEX [IX_MP_Notification_Audit_TargetUserDateCreated] ON [dbo].[MP_Notification_Audit]
(
    [target_user_id] ASC,
    [date_created] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
GO

表中有超过 10000 行,其中 a[target_user_id]为100017.

当我执行以下查询时:

SELECT
    [target_user_id], [patient_id]
FROM
    [dbo].[MP_Notification_Audit]
WHERE
    [target_user_id] = 100017
ORDER BY
    [date_created] ASC
OFFSET 9200 ROWS
FETCH NEXT 10 ROWS ONLY

...我得到以下实际执行计划:

执行计划1

为什么 SQL Server 需要进行 9210 次而不是 10 次聚集键查找?索引[IX_MP_Notification_Audit_TargetUserDateCreated]应该允许它找出它需要检索到的 10 个 RID [patient_id],并且只进行 10 个聚集键查找,对吗?

我还发现了一些更奇怪的行为——看起来 SQL Server 会因为你没有选择不可索引的列而“惩罚”你。如果我改为OFFSET10000 行,我会得到以下执行计划:

SELECT
    [target_user_id], [patient_id]
FROM
    [dbo].[MP_Notification_Audit]
WHERE
    [target_user_id] = 100017
ORDER BY
    [date_created] ASC
OFFSET 10000 ROWS
FETCH NEXT 10 ROWS ONLY

执行计划2

...建议创建一个包含 的索引[patient_id],并对整个表进行低效的聚集索引扫描。花费的时间是 0.126 秒,但这显然会好很多,因为当我将不可索引的列添加[title]到查询中时,我得到了这个:

SELECT
    [target_user_id], [patient_id], [title]
FROM
    [dbo].[MP_Notification_Audit]
WHERE
    [target_user_id] = 100017
ORDER BY
    [date_created] ASC
OFFSET 10000 ROWS
FETCH NEXT 10 ROWS ONLY

执行计划3

...并且仍然使用非聚集索引,所用时间仅为0.032s。SQL Server 是否基本上说“您本可以创建一个索引来更有效地执行此操作,所以我们甚至不会使用您拥有的索引,我们会低效地进行查找来惩罚您”,或者我我错过了什么?

sql-server query-performance
  • 2 2 个回答
  • 248 Views

2 个回答

  • Voted
  1. J.D.
    2021-03-06T05:02:22+08:002021-03-06T05:02:22+08:00

    这是因为执行查询的子句的操作顺序。WHERE子句出现在OFFSETandFETCH子句之前。这就是为什么不仅有大约 10,000 个(在您的第一个示例中为 9,000 个)key lookups,而且还有同样多的index seeks。这是由于target_user_id您的WHERE子句中的谓词而发生的过滤。执行计划从右到左读取事件的顺序,因此,如果您按照从索引开始的计划查找并按您的方式向左工作,您将看到Top运算符,它代表您的OFFSET/FETCH子句,因此位于您的WHERE子句之后。

    WHERE简单地说,SQL Server 首先需要根据您通过谓词(在、HAVING和JOIN子句中)应用的过滤器来定位行。然后,一旦找到正确的行,它就可以应用您的OFFSET/FETCH子句。如果它没有首先根据您的过滤器获取所有行,它就不会知道OFFSET/需要哪些行FETCH。

    在这种情况下,SQL Server 引擎是否可以在逻辑上更好地编程?可能。他们可以以这样一种方式设计引擎,即实现Top操作最终将在该索引搜索的数据上发生,并在键查找发生之前应用它以至少最小化键查找的数量,但我想它只会在某些情况下有助于提高性能,并以编程方式使事情变得更加复杂,以至于它可能不值得。例如,键查找与索引查找操作并行发生(因此它们在计划步骤中同样位于右侧)。如果顶部操作要在键查找之前发生以减少数据,那么剩余的行键查找必须在该Top操作之后连续发生,结果也是在索引查找操作之后连续发生,这在某些情况下性能会更差。


    关于第二个问题,关于执行计划从索引搜索更改为聚集索引扫描再回到索引搜索的经历,这被称为引爆点。基本上,查询优化器会分析您的查询,并在可用于获取数据的一系列不同执行计划中快速计算每个操作的成本,部分基于 SQL Server 维护的数据缓存统计信息。这些成本的总和允许查询优化器选择成本最低的执行计划(理想情况下是最快的执行计划))。

    这些缓存的统计信息基于表中总行数中每列中每个值存在的行数,并用于对需要发生的每个操作进行基数估计。简而言之,基数估计是 SQL Server 认为特定操作将返回的行数。例如,WHERE第一个示例中的子句返回大约 9,000 行,SQL Server 引擎可能估计基数接近该数字,这导致索引查找操作的成本足以选择索引查找。

    当您明确告诉 SQL Server 改为返回 10,000 行时,它可能会触发Tipping Point,这使得它认为它需要返回的行数将通过索引扫描操作更有效,因为行的基数足够高SQL 引擎认为扫描可能会有效地遇到更连续的行,而不是进行 10,000 次索引查找。(长话短说,优化器并不完美,尤其是在接近临界点时,索引搜索在这里可能仍然更有效。)

    至于为什么当您在SELECT列表中添加另一个未索引的列时会导致查询优化器选择一个不超过临界点的计划,我无法确切地告诉您(尤其是在我面前没有实际执行计划的情况下),因为它很漂亮使幕后发生的事情变得复杂,但一般来说,它所做的估计是基于您当时数据的缓存统计信息,导致它计算的成本不再超过临界点,并导致它选择一个利用索引搜索的执行计划。

    • 3
  2. Best Answer
    Dan Guzman
    2021-03-06T05:26:37+08:002021-03-06T05:26:37+08:00

    我同意优化器可以更聪明地使用OFFSET和来确定计划中的密钥查找位置FETCH。

    作为一种解决方法,您可以使用如下所示的 CTE。

    WITH Top10Keys AS (
        SELECT
            ID, date_created
        FROM
            [dbo].[MP_Notification_Audit]
        WHERE
            [target_user_id] = 100017
        ORDER BY
            [date_created] ASC
        OFFSET 9200 ROWS
        FETCH NEXT 10 ROWS ONLY
    )
    SELECT
        [target_user_id], [patient_id]
    FROM Top10Keys
    JOIN [dbo].[MP_Notification_Audit] AS AdditionalData ON AdditionalData.id = Top10Keys.id
    ORDER BY Top10Keys.date_created;
    

    然后在计划TOP中的运算符之后完成嵌套循环键查找。

    图形计划

    • 3

相关问题

  • SQL Server - 使用聚集索引时如何存储数据页

  • 我需要为每种类型的查询使用单独的索引,还是一个多列索引可以工作?

  • 什么时候应该使用唯一约束而不是唯一索引?

  • 死锁的主要原因是什么,可以预防吗?

  • 如何确定是否需要或需要索引

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目

    • 12 个回答
  • Marko Smith

    如何让sqlplus的输出出现在一行中?

    • 3 个回答
  • Marko Smith

    选择具有最大日期或最晚日期的日期

    • 3 个回答
  • Marko Smith

    如何列出 PostgreSQL 中的所有模式?

    • 4 个回答
  • Marko Smith

    列出指定表的所有列

    • 5 个回答
  • Marko Smith

    如何在不修改我自己的 tnsnames.ora 的情况下使用 sqlplus 连接到位于另一台主机上的 Oracle 数据库

    • 4 个回答
  • Marko Smith

    你如何mysqldump特定的表?

    • 4 个回答
  • Marko Smith

    使用 psql 列出数据库权限

    • 10 个回答
  • Marko Smith

    如何从 PostgreSQL 中的选择查询中将值插入表中?

    • 4 个回答
  • Marko Smith

    如何使用 psql 列出所有数据库和表?

    • 7 个回答
  • Martin Hope
    Jin 连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane 如何列出 PostgreSQL 中的所有模式? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh 为什么事务日志不断增长或空间不足? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland 列出指定表的所有列 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney MySQL 能否合理地对数十亿行执行查询? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx 如何监控大型 .sql 文件的导入进度? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison 你如何mysqldump特定的表? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 对 SQL 查询进行计时? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas 如何从 PostgreSQL 中的选择查询中将值插入表中? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 列出所有数据库和表? 2011-02-18 00:45:49 +0800 CST

热门标签

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve