【问题标题】:How to improve Clustered Index scan to Clustered Index seek?如何改进聚集索引扫描到聚集索引搜索?
【发布时间】:2022-01-04 11:41:08
【问题描述】:

我有两张桌子

[dbo].[Employee](
    [EmployeeID] [int] IDENTITY(1,1) NOT NULL,
    [Title] [varchar](50) NULL,
    [Role] [int] NULL,
    [Status] [varchar](1) NULL)

[dbo].[Roles](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [Role] [varchar](25) NULL,
    [Description] [varchar](25) NULL)

通过 id 使用主集群键。 我也有存储过程

SELECT
   [EmployeeID]
  ,[Title]
  ,employee.[Role]
  ,roles.Role AS RoleName
FROM [dbo].Employee AS employee
INNER JOIN [dbo].Roles AS roles ON roles.id = employee.Role
WHERE [Status] <> 'D'

执行计划向我展示了我想要避免的“聚集索引扫描”。有什么办法可以将其转换为“聚集索引搜索”? Execution Plan screen

db fiddle>

【问题讨论】:

  • 不是那个查询,不是。 CLUSTERED INDEXid 上(我假设EmployeeID),但您正在过滤Status 的值。如果您在status 上有一个覆盖索引,RDBMS 可以使用它来查找,尽管考虑到您使用的是&lt;&gt;,它可能选择不这样做。
  • 步骤1)添加角色外键。
  • 您可以尝试过滤索引Employee (Role, EmployeeID) INCLUDE (Status, Title) WHERE (Status &lt;&gt; 'D')。如前所述,这里不需要加入,如果你有外键,就会被省略
  • 学习良好的习惯。不要不一致地使用括号。除非绝对必要,否则我建议您不要使用它们,因为它们会使您的代码难以阅读。通常 SELECT 查询应该有一个 ORDER BY 子句。请使用语句终止符。具有除标识主键列之外的所有可为空列的表在逻辑上存在缺陷。 1 个字符的列究竟是什么变量?想想你的数据类型。
  • @SMor "一般SELECT 查询应该有一个ORDER BY 子句" 你从哪里得到的?是的,如果客户端应用程序期望行按他们应该指定的特定顺序排列,但应用程序应该被编写为对顺序不可知并且不应该关心,这减少了服务器上不必要的排序。同意其余的

标签: sql sql-server indexing sql-execution-plan


【解决方案1】:

正如我在 cmets 中提到的,dbo.Employees 上的 CLUSTERED INDEX 在这方面对您没有帮助。这样做的原因是因为您正在过滤列status,它只是包含CLUSTERED INDEX 中,但没有对其排序。因此,RDBMS 只能扫描整个表以过滤掉Status 的值为D 的行。

您可以在 Status 列和 INCLUDE 其他列上添加 INDEX,这可能会导致索引查找(不是聚集索引查找),但是,由于如果您使用&lt;&gt; D,那么 RDBMS 可能仍会选择执行扫描;是否确实取决于您的数据分布:

CREATE INDEX IX_EmployeeStatus ON dbo.Employee (Status) INCLUDE (Title, Role);

另外添加FOREIGN KEY 到您的表中:

ALTER TABLE dbo.Employee ADD CONSTRAINT FK_EmployeeRole FOREIGN KEY (Role) REFERENCES dbo.Roles (id);

db<>fiddle

【讨论】:

    【解决方案2】:

    如果您的状态列上有一个索引,那么该代码应该会导致两次索引查找。一个用于'D'。你只需要一个关于 Status 的索引。

    您可以使用临时表将其转换为一次搜索,将您的“Not”转换为“is”,这在查询调优中始终是一个好主意:

    CREATE TABLE #StatusMatch (StatusCode varchar(1) NOT NULL PRIMARY KEY CLUSTERED)
     INSERT INTO #StatusMatch WITH(TABLOCKX)
          SELECT Status 
            FROM dbo.Employee WITH(NOLOCK)
           WHERE Status <> 'D'
        GROUP BY Status
        ORDER BY Status;
    
          SELECT a.EmployeeID, a.Title, a.Role, b.Description AS RoleName
            FROM dbo.Employee a WITH(NOLOCK)
      INNER JOIN dbo.Roles b WITH(NOLOCK) ON a.Role = b.id
      INNER JOIN #StatusMatch c ON a.Status = c.StatusCode
    

    【讨论】:

    • &lt;&gt; 'D' 可以使用索引查找运算符来完成。 SQL Server 可以并且确实将&lt;&gt; 谓词转换为两个范围搜索(在&lt; 'D'&gt; 'D' 上)。在 OP 的情况下没有合适的索引允许这样做
    • 你为什么在这里发垃圾邮件NOLOCK?如果您正在鼓励使用它(您似乎是)至少警告 OP 他们也需要注意的所有问题或注意事项:Bad habits : Putting NOLOCK everywhere。尽管NOLOCK 不是您可能认为的神奇的“更快”按钮。同样执行INSERT INTONOLOCK 通常是一个可怕 的想法。另外,为什么要在 only 范围是创建它的范围的临时表上使用 TABLOCKX
    • 如果你愿意,我可以再给你几篇文章,如果你愿意,可以更多地讨论它的问题,@OwlFace。 123。关键是它不会提高性能;它采取“捷径”并忽略确保数据可靠性的“安全”功能。查询可能更快,但也可能非常错误;这更糟糕。
    • 显然你知道NOLOCK是如何工作的,如果你知道你会推荐SNAPSHOTTABLOCK,这取决于你是否期望并发写入,两者这些在一致性方面远远优于NOLOCK,并提供几乎相同的性能提升。 NOLOCK 可能导致:行甚至整个页面被遗漏或重复读取,行唯一、主要、外部甚至检查约束失败,本应唯一的双重连接,损坏的 LOB 数据,并且可以 仍然导致 DDL 阻塞(在索引重建或同步统计更新时很容易发生)
    • 上述链接中有 2 个不是来自 Brent Ozar。更多文章:by an actual MS employee 劝阻它,by Paul White Database Administrators 上的一个模组,以及 another by A Betrand showing completely corrupt LOB data
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-01-27
    • 2019-08-26
    • 1970-01-01
    • 2014-07-28
    • 2010-11-24
    • 2021-04-22
    • 2014-12-18
    相关资源
    最近更新 更多