为什么SQL会在我的案例中选择不正确的索引?

时间:2009-05-18 07:39:51

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

我有一张有两个指数的表格;一个是多列聚簇索引,在3列上:

(
   symbolid int16,
   bartime int32,
   typeid int8
) 

第二个是非群集

(
   bartime int16
)

我正在尝试运行的select语句是:

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

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

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

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

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

    symbolid in(1010,1020,1030,1040,1050,1060)

         

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

         

    ((1010和1060之间)的符号)

    测试后

    我发现当我使用IN更改where条件时使用> =和< = bartime列上的非聚集索引比3列上的聚簇索引更好。

    所以我有两种情况,如果WHERE使用IN,最好使用聚集索引,如果它包含> =和< =它使用第二种情况。

6 个答案:

答案 0 :(得分:3)

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 …

但是如果BETWEEN上的symbolid范围无法构建如此有限数量的谓词,那就是它恢复效率较低的Clustered Index Scansymbolid扫描的原因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}}列表并加入此列表。

答案 1 :(得分:0)

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

答案 2 :(得分:0)

尽可能使用symbolid BETWEEN 1010 AND 1050。使用BETWEEN=>=>< n或<=或这些与AND的组合与使用ORIN相比,通常会带来更好的效果和更好的索引选择。

答案 3 :(得分:0)

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

我可能会从介绍语句开始,但只测试您的数据,服务器,索引等将证明是最好的情况。

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

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

答案 4 :(得分:0)

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

答案 5 :(得分:0)

您的查询引用四个列:

  • 符号ID
  • vTrdBuy
  • TYPEID
  • barDateTime

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

  • 符号ID
  • vTrdBuy
  • TYPEID
  • barDateTime

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

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

查看查询时,两列非常具体,用于限制要返回的行:

WHERE typeID = 1
AND barDateTime = 44991 

创建以 typeID barDateTime 开头的索引,对于帮助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想要的所有内容,而且你不必搞乱物理表的物理排序(即集群)。