繁体   English   中英

在SQL Server中为常用查询创建适当的索引

[英]Creating an appropriate index for a frequently used query in SQL Server

在我的应用程序中,我有两个经常使用的查询。 这些查询的Where子句如下:

WHERE FieldA = @P1 AND (FieldB = @P2 OR FieldC = @P2)

WHERE FieldA = @P1 AND FieldB = @P2

P1P2是在UI中输入或来自外部数据源的参数。

  • FieldA是一个int并且非常独特,意味着:表中只有两个,三个,四个不同的值,比如20000行
  • FieldB是一个varchar(20)并且“几乎”是唯一的,只有很少的行,其中FieldB可能具有相同的值
  • FieldC是一个varchar(15) ,也非常独特,但没有FieldB那么多
  • FieldA和FieldB一起是唯一的(但不构成我的主键,这是一个带有聚簇索引的简单自动递增标识列)

我现在想知道定义索引以最好地加速这两个查询的最佳方法是什么。 我应该用...定义一个索引

FieldB (or better FieldC here?)
FieldC (or better FieldB here?)
FieldA

...或更好的两个指数:

FieldB
FieldA

FieldC
FieldA

还是有其他更好的选择吗? 什么是最好的方式和原因?

提前感谢您的建议!

编辑:

正如其他读者的信息:这是另一个已被删除的答案。 实际上答案对我来说似乎非常有用。 建议是创建两个索引(根据我上面的第二个选项)并使用两个select语句的UNION重新WHERE FieldA = @P1 AND FieldB = @P2第一个查询(一个使用WHERE FieldA = @P1 AND FieldB = @P2 ,另一个使用WHERE FieldA = @P1 AND FieldC = @P2 )而不是OR以从两个索引中受益(OR运算符不是这种情况)。

EDIT2:

使用OR并且未使用索引并且UNION更可取的声明似乎是错误的 - 至少根据我自己的测试(参见下面我自己的答案)。

扩展Remus'(编辑:现已删除)答案......

  • 如果@ p2是varchar(15)那么你无法可靠地与FieldB进行比较,它是varchar(20)
  • 如果@ p2是varchar(20),则FieldC将转换为varchar(20)而不使用索引(或者最好扫描它)
  • 如果@ p1只有2个,3个,4个值那么为什么不是tinyint并减少表/索引大小?

在解决此数据类型优先级问题之前,我不打算使用索引:这是在OR子句问题之上。

最后,列是唯一的或非唯一的:两者之间没有。 统计数据在这里有助于选择性,但它无关紧要。

由于FieldA的选择性FieldC, FieldA我会将Remus回答的索引反转为FieldB, FieldA (和唯一)和FieldC, FieldA

注释后编辑:你无法比较使用@ p2和使用常量字符串。

在使用更大的数据库(在SQL Server 2008中)进行一些测试后,我添加了自己的答案:

首先,我决定使用第二个选项,意思是,我创建了两个索引:

CREATE UNIQUE NONCLUSTERED INDEX [IX_B] ON [dbo].[MyTable] 
(
    [FieldB] ASC,
    [FieldA] ASC
)
CREATE NONCLUSTERED INDEX [IX_C] ON [dbo].[MyTable] 
(
    [FieldC] ASC,
    [FieldA] ASC
)

我已经测试了两个查询:

declare @p1 int = 1;
declare @p2 varchar(20) = '12345678';

select * from MyTable
where FieldA=@p1 and (FieldB=@p2 or FieldC=@p2);

执行此查询我得到以下查询计划( ID是表的主键, PK_MyTable主键上的聚簇索引):

|--Nested Loops(Inner Join, OUTER REFERENCES:([MyDb].[dbo].[MyTable].[ID]))
   |--Stream Aggregate(GROUP BY:([MyDb].[dbo].[MyTable].[ID]) DEFINE:([MyDb].[dbo].[MyTable].[FieldA]=ANY([MyDb].[dbo].[MyTable].[FieldA])))
   |  |--Merge Join(Concatenation)
   |     |--Index Seek(OBJECT:([MyDb].[dbo].[MyTable].[IX_B]), SEEK:([MyDb].[dbo].[MyTable].[FieldB]=[@p2] AND [MyDb].[dbo].[MyTable].[FieldA]=[@p1]) ORDERED FORWARD)
   |     |--Index Seek(OBJECT:([MyDb].[dbo].[MyTable].[IX_C]), SEEK:([MyDb].[dbo].[MyTable].[FieldC]=[@p2] AND [MyDb].[dbo].[MyTable].[FieldA]=[@p1]) ORDERED FORWARD)
   |--Clustered Index Seek(OBJECT:([MyDb].[dbo].[MyTable].[PK_MyTable]), SEEK:([MyDb].[dbo].[MyTable].[ID]=[MyDb].[dbo].[MyTable].[ID]) LOOKUP ORDERED FORWARD)

因此似乎使用了两个索引(“索引搜索”)。

查询的经过时间:00:00:00.2220127

我测试的第二个查询是使用JOIN来避免OR运算符(请参阅我的问题中的“编辑”):

declare @p1 int = 1;
declare @p2 varchar(20) = '12345678';

select * from MyTable where FieldA=@p1 and FieldB=@p2
union
select * from MyTable where FieldA=@p1 and FieldC=@p2;

此查询具有以下查询计划:

|--Merge Join(Union)
   |--Nested Loops(Inner Join, OUTER REFERENCES:([MyDb].[dbo].[MyTable].[ID]))
   |  |--Index Seek(OBJECT:([MyDb].[dbo].[MyTable].[IX_B]), SEEK:([MyDb].[dbo].[MyTable].[FieldB]=[@p2] AND [MyDb].[dbo].[MyTable].[FieldA]=[@p1]) ORDERED FORWARD)
   |  |--Clustered Index Seek(OBJECT:([MyDb].[dbo].[MyTable].[PK_MyTable]), SEEK:([MyDb].[dbo].[MyTable].[ID]=[MyDb].[dbo].[MyTable].[ID]) LOOKUP ORDERED FORWARD)
   |--Nested Loops(Inner Join, OUTER REFERENCES:([MyDb].[dbo].[MyTable].[ID]))
      |--Index Seek(OBJECT:([MyDb].[dbo].[MyTable].[IX_C]), SEEK:([MyDb].[dbo].[MyTable].[FieldC]=[@p2] AND [MyDb].[dbo].[MyTable].[FieldA]=[@p1]) ORDERED FORWARD)
      |--Clustered Index Seek(OBJECT:([MyDb].[dbo].[MyTable].[PK_MyTable]), SEEK:([MyDb].[dbo].[MyTable].[ID]=[MyDb].[dbo].[MyTable].[ID]) LOOKUP ORDERED FORWARD)

同样使用两个索引(“索引搜索”)。

查询的经过时间:00:00:00.3710212

注意:对于两个查询,我声明@ p2的长度无关紧要:使用varchar(8)或varchar(20)或varchar(30)可以得到相同的结果和查询计划。

根据这些结果,我将继续使用OR运算符而不是UNION,因为两个查询都使用索引但第一个更快。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM