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

时间:2010-06-13 16:55:42

标签: sql sql-server database sql-server-2008 indexing

在我的应用程序中,我有两个经常使用的查询。这些查询的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

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

提前感谢您的建议!

修改

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

EDIT2:

没有使用OR并且UNION更好的声明似乎是错误的 - 至少根据我自己的测试(参见我自己的答案)。

2 个答案:

答案 0 :(得分:3)

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

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

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

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

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

在评论之后编辑:你无法比较使用@ p2和使用常量字符串。

答案 1 :(得分:0)

在使用更大的数据库(在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,因为两个查询都使用索引但第一个更快。