【问题标题】:Simple Inner join suggesting an Include index建议包含索引的简单内连接
【发布时间】:2018-01-23 05:28:35
【问题描述】:

我有这个简单的内部连接查询,它的执行计划主表有大约 34K 条记录,明细表有大约 51K 条记录。但是这个简单的查询建议使用include 添加一个索引(包含我在选择中包含的所有主列)。我没想到这可能是原因和补救措施。

   DECLARE    
       @StartDrInvDate Date ='2017-06-01',
       @EndDrInvDate Date='2017-08-31'

   SELECT
       Mastertbl.DrInvoiceID,
       Mastertbl.DrInvoiceNo,
       Mastertbl.DistributorInvNo,
       PreparedBy,
       detailtbl.BatchNo, detailtbl.Discount,
       detailtbl.TradePrice, detailtbl.IssuedUnits,
       detailtbl.FreeUnits
   FROM 
       scmDrInvoices Mastertbl
   INNER JOIN 
       scmDrInvoiceDetails detailtbl ON Mastertbl.DrInvoiceID = detailtbl.DrInvoiceID
   WHERE
       (Mastertbl.DrInvDate BETWEEN @StartDrInvDate AND @EndDrInvDate)

我真正的好奇是为什么它建议使用这个索引 - 我通常不会在较大的表中看到这种行为

【问题讨论】:

  • 如果您对当前的性能感到满意,那么您可以放心地忽略该建议。
  • 仅仅因为 SQL Server 要求这样做并不意味着您应该创建它。它基于它在该特定查询上使用的执行计划进行假设。尝试在 Mastertbl.DrInvoiceID 上添加非聚集索引并包含 Mastertbl.DrInvDate。查看查询并在其上播放索引:)
  • @Serg 这个查询实际上是较大查询的一部分,它需要一些时间,当我删除所有其他连接时,该查询建议这个索引,我只剩下这个,仍然建议相同的索引跨度>
  • 如果性能是一个问题,我肯定会尝试遵循建议。为什么优化器不建议覆盖其他查询的索引完全取决于这些查询。

标签: sql sql-server sql-server-2012 query-performance sql-execution-plan


【解决方案1】:

您也许可以看到为什么它建议索引发票日期的逻辑;它从它认为当前存在的行数中对您想要的行数进行了一些计算,并且似乎该列上索引的选择性使其值得索引。如果您想要 55,000 行中的 3 行,并且您希望它永远每 5 分钟一次,那么索引是有意义的。特别是如果该表的增长率意味着明年它将是 550 万行中的 3 行。

包含建议可能更天真地建议将足够的附加数据与索引值相关联,以便可以从索引中回答主表所需的整个数据集,而不会触及表 - 索引本质上是指向表中行的指针;当查询引擎使用索引来定位它需要的所有行时,它仍然需要对表进行 bash 以实际获取所需的数据。通过在索引中包含数据,您无需访问表,这有时是明智的,但在其他情况下则不然(为很少运行的查询创建许多基本上复制大部分/所有表数据的索引是对磁盘空间的浪费)。

还要考虑一下,您现在在调试工具中运行此查询的频率正在影响 SQLServer 对查询使用频率的看法。我经常发现我的 SQLAzure 门户提出索引建议,这要归功于开发人员一遍又一遍地运行查询,调试它,当我真正知道在 prod 中,该查询将每月使用一次时,所以我放弃了建立索引的建议包括大多数表,当直接“仅索引搜索的列”就可以了,不需要包括

因此,不应盲目听从这些建议,因为 SQLServer 无法知道您打算在实际应用程序中使用什么或类似查询。索引的创建和维护应该仔细周到地进行;例如,可能是这个查询正在请求这个索引,另一个查询可能想要一个不同列上的索引,但是创建一个索引可能有意义,该索引在两个列上(以特定顺序)作为键,然后在任何查询中搜索在第二个索引的列上,包含一个谓词,该谓词命中第一个索引列,无论查询是否需要它

例如,在您的 invoices 表中,您有一列指示它是否已付款,而在您的应用程序的其他地方,您还有另一个查询来计算未付发票的数量。您可以有 2 个索引 - 一个在发票日期(用于此查询)和一个在状态(用于该查询)或一个在两列(状态、日期)上,并且在此查询中具有 WHERE status = 'unpaid' AND date between... 谓词,即使状态谓词是多余的。为什么它可能是多余的?假设您知道您只会选择上周尚未发出的发票,因此只能是未付款的。这就是我所说的“考虑索引”的意思 - 您对您的应用了解很多SQLServer 永远无法弄清楚.. 通过在“从上周获取发票”查询中包含冗余状态列(即使状态在逻辑上是冗余的),您允许查询引擎使用首先按状态排序的索引,然后按日期排序.这意味着您不必只维护一个索引,它可以被两个查询使用

索引维护和创建逻辑可以是一项全职工作.. ;)

【讨论】:

    【解决方案2】:

    对于这个查询:

    SELECT m.DrInvoiceID, m.DrInvoiceNo, m.DistributorInvNo,
           PreparedBy,
           d.BatchNo, d.Discount, d.TradePrice, d.IssuedUnits, d.FreeUnits
    FROM scmDrInvoices m INNER JOIN 
         scmDrInvoiceDetails d
         ON m.DrInvoiceID = d.DrInvoiceID
    WHERE m.DrInvDate BETWEEN @StartDrInvDate AND @EndDrInvDate;
    

    我希望基本索引是:scmDrInvoices(DrInvDate, DrInvoiceID)scmDrInvoiceDetails(DrInvoiceID)。该索引将允许查询引擎快速识别主表中与WHERE 匹配的行,然后在scmDrInvoiceDetails 中查找相应的值。

    然后可以将其余列包含在任一索引中,以便索引覆盖查询。 “覆盖”表示所有列都在索引中,因此查询计划不需要参考原始数据页。

    上述策略是 SQL Server 所建议的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多