在我的应用程序中,我有两个经常使用的查询。这些查询的Where子句如下:
WHERE FieldA = @P1 AND (FieldB = @P2 OR FieldC = @P2)
和
WHERE FieldA = @P1 AND FieldB = @P2
P1
和P2
是在UI中输入或来自外部数据源的参数。
int
,非常独特,意味着:表中只有两个,三个,四个不同的值,表示20000行varchar(20)
并且“几乎”是唯一的,FieldB可能只有很少的行可能具有相同的值varchar(15)
,也非常独特,但没有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更好的声明似乎是错误的 - 至少根据我自己的测试(参见我自己的答案)。
答案 0 :(得分:3)
扩展Remus'(编辑:现已删除)答案......
在解决此数据类型优先级问题之前,我不会打扰索引:这是在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,因为两个查询都使用索引但第一个更快。