【问题标题】:Why does SQL choose an incorrect index in my case?为什么在我的情况下 SQL 选择了不正确的索引?
【发布时间】:2010-10-26 23:18:39
【问题描述】:

我有一个有两个索引的表;一个是多列聚集索引,在 3 列上:

(
   symbolid int16,
   bartime int32,
   typeid int8
) 

第二个是非聚集在

(
   bartime int16
)

我试图运行的选择语句是:

    SELECT symbolID, vTrdBuy
    FROM mvTrdHidUhd 
    WHERE typeID = 1 
    AND barDateTime = 44991 
    AND symbolid in (1010,1020,1030,1040,1050,1060) 

我在 sql2008 上使用 sql management studio 编辑器运行此查询并启用实际执行计划,我发现 sql 使用第二个索引和 propse 为三列(symbolid、bartime、typeid)创建了一个新索引但非聚集! !! (我认为它说的是非聚集索引,因为已经有一个聚集索引)

这个选择是错误的,我再次重新运行相同的查询并强制 SQL 使用聚集索引(使用“with index”)并且性能更好。

我在这里有两个问题,一个与此行为有关,第二个与查询本身有关

  1. 为什么SQL选择了错误的索引并提出了相同的索引
  2. 我应该在 "where" 条件下使用哪一个以获得更好的性能

符号在 (1010,1020,1030,1040,1050,1060)

(symbolid = 1010 或 symbolid = 1020 ..etc)

(符号介于 (1010 和 1060) 之间)

测试后

我发现当我将 where 条件从使用 IN 更改为使用 >= 和

所以我有两种情况,如果 WHERE 使用 IN,最好使用聚集索引,如果它包含 >= 并且

【问题讨论】:

    标签: sql-server tsql sql-server-2008 sql-execution-plan


    【解决方案1】:
    SELECT  symbolID, vTrdBuy
    FROM    mvTrdHidUhd 
    WHERE   typeID = 1 
            AND barDateTime = 44991 
            AND symbolid IN (1010,1020,1030,1040,1050,1060)
    

    此条件不在您的聚集索引的单个连续范围内。

    这些行:

    1010, 44991, 1
    1010, 50000, 1
    1020, 44991, 1
    

    将在索引中按顺序排列,但您的查询将选择第一个和第三个,跳过第二个。

    SQL Server 可以使用 Clustered Index Seek 如果谓词数量有限,例如在您的 IN 案例中。在这种情况下,它使用了多个范围:

    SELECT  symbolID, vTrdBuy
    FROM    mvTrdHidUhd 
    WHERE   (typeID = 1 
            AND barDateTime = 44991 
            AND symbolid = 1010)
            OR
            (typeID = 1 
            AND barDateTime = 44991 
            AND symbolid = 1010)
            OR …
    

    但是在symbolid 上的BETWEEN 范围的情况下,它无法构造如此有限数量的谓词,这就是为什么它会恢复到效率较低的Clustered Index Scan(它会在symbolid 上扫描并过滤掉错误的结果)。

    在这种情况下,您的非聚集索引性能更好。

    你可以这样重写你的查询:

    SELECT  symbolID, vTrdBuy
    FROM    (
            SELECT  DISTINCT symbolid
            FROM    mvTrdHidUhd 
            WHERE   symbolid BETWEEN 1010 AND 1050
            ) s
    JOIN    mvTrdHidUhd m
    ON      m.symbolid = s.symbolid
            AND m.typeID = 1 
            AND m.barDateTime = 44991
    

    ,它也将在您的表上使用Clustered Index Seek,以构建DISTINCT symbolid 的列表并加入此列表。

    【讨论】:

    • 你说的是我说的,但是有数据的例子。 +1
    • 感谢您的精彩解释,但您没有回答我的第一个问题?
    • @Ahmed:它选择非聚集索引是因为它认为它会更有选择性。至于建议:您能否检查一下 SQL Server 建议的列顺序是否完全相同?
    【解决方案2】:

    您的查询引用了 列:

    • 符号ID
    • vTrdBuy
    • 类型ID
    • barDateTime

    虽然聚集索引只涵盖其中三个

    • 符号ID
    • vTrdBuy
    • 类型ID
    • barDateTime

    SQL Server 忽略该索引的原因是它对它毫无用处。索引首先按symbolID 排序,您不需要特定的symbolID,而是一堆随机值。这意味着它必须读取整个表格。

    聚集索引中的下一列是vTrdBuy。这不是用来帮助它跳到它真正想要的行。

    查看查询,有两列非常具体地限制了您要返回的行:

    WHERE typeID = 1
    AND barDateTime = 44991 
    

    创建以 typeIDbarDateTime 开头的索引对于帮助 SQL Server 跳转到您感兴趣的行非常有用.

    第一个 SQL Server 可以直接跳转到

    typeID = 1. 
    

    到达那里后,它可以直接跳到酒吧所在的位置

    barDateTime = March 8, 2023
    

    它可以通过直接搜索索引来做到这一点,因为索引是按其中的列排序的。这是非常快的,并且它消除了大多数行的查看。

    如果您要创建索引:

    (
       typeID
       barDateTime
       symbolID
    )
    

    如果查询返回很多行,它仍然可能没有用。为了完成 SELECT 语句,SQL Server 仍然需要 vTrdBuy 值。它必须通过在表中跳转每一行匹配条件(称为书签查找)来做到这一点。如果行数过多(例如 > 500),SQL Server 将忘记索引并仅扫描整个表 - 因为它会更快。

    您想阻止书签查找,通过让它不必返回表中查找缺失值,您希望将值包含在索引中:

    CREATE INDEX IX_mvTrdHidUhd_FancyCovering ON mvTrdHidUhd 
    (
       typeID, barDateTime, symbolID, vTrdBuy
    )
    

    现在您有了一个索引,它包含 SQL Server 想要的所有内容,按照它想要的顺序,并且您不必弄乱物理表的物理排序顺序(即集群)。

    【讨论】:

    • 根据定义,聚集索引总是覆盖所有查询。错误的说法是:“虽然聚集索引只涵盖其中三个 * symbolID * vTrdBuy * typeID * barDateTime”
    • 你怎么知道聚集索引总是覆盖所有列?
    • 聚集索引的节点是数据。这意味着它始终涵盖所有内容,因为最后要加载数据行,您必须使用聚集索引。
    • @TomTom 当我提到列是 "in" 聚集索引时,我指的是聚集索引中的列。例如:CREATE CLUSTERED INDEX IX_Orders_SymbolID_TypeID_BarDate ON Orders (SymbolID, TypeID, BarDateTime)。不在该聚集索引中的列不在该聚集索引中。 SQL Server 中的聚集索引指的是数据的物理排序顺序,根据定义,所有数据都在表中。但并非所有数据都在索引中 - 即并非所有列都用于确定物理排序顺序。这些迂腐的做法都无助于解决 OP 的问题。
    【解决方案3】:

    您也可以在 (symbolid,bartime,typeid,mvTrdBuy) 上尝试覆盖索引

    【讨论】:

      【解决方案4】:

      索引列的顺序可能会影响优化器是否会选择您的索引。您指出索引是 (symbolid int16,bartime int32,typeid int8) 但 symbolid 是 where 子句中最不明显的值。这将需要对您拥有的 6 个值进行 6 次索引查找。

      我可能会从 between 语句开始,但只有使用您的数据、服务器、索引等进行测试才能证明是最好的情况。

      如果您要创建另一个索引,请尝试这些列的另外 2 个订单。

      如其他地方所述,更新您的统计信息

      【讨论】:

      • 首先在 where 子句中选择最严格的列(输出最少的行)。
      【解决方案5】:

      如果可能,请使用symbolid BETWEEN 1010 AND 1050。使用BETWEEN=>=><n 或<= 或将它们与AND 结合使用通常比使用@ 带来更好的性能和更好的索引选择987654329@或IN

      【讨论】:

        【解决方案6】:

        更新表/索引的统计信息可能使其选择正确的索引

        【讨论】:

        • 我会测试它,但这意味着我必须强制 sql 使用正确的索引而不是依赖它的选择?
        • 否,如果您的统计信息正确并且您的索引和查询设计正确,那么您不需要在查询中强制使用某些索引。
        猜你喜欢
        • 2015-01-16
        • 2022-01-05
        • 2018-03-07
        • 2022-01-14
        • 1970-01-01
        • 2022-01-21
        • 2014-01-15
        • 2016-02-03
        • 1970-01-01
        相关资源
        最近更新 更多