SQL Server 为啥索引不与 OR 一起使用

Posted

技术标签:

【中文标题】SQL Server 为啥索引不与 OR 一起使用【英文标题】:SQL Server why is index not used with ORSQL Server 为什么索引不与 OR 一起使用 【发布时间】:2016-11-29 19:50:54 【问题描述】:

我一直在研究索引并试图了解它们的工作原理以及如何使用它们来提高性能,但我遗漏了一些东西。

我有下表:

人物

| Id | Name | Email | Phone |
| 1  | John |  E1   |  P1   |
| 2  | Max  |  E2   |  P2   |

考虑到查询(大部分时间)将采用以下形式,我正在尝试找到索引列 EmailPhone 的最佳方法

[1] SELECT * FROM Person WHERE Email = '...' OR Phone = '...'
[2] SELECT * FROM Person WHERE Email = ...
[3] SELECT * FROM Person WHERE Phone = ...

我认为最好的方法是使用两列创建一个索引:

CREATE NONCLUSTERED INDEX [IX_EmailPhone]
ON [dbo].[Person]([Email], [PhoneNumber]);

但是,对于上面的索引,只有查询 [2] 受益于索引查找,其他查询使用索引扫描。

我还尝试创建多个索引:一个包含两列,一个用于电子邮件,一个用于电子邮件。在这种情况下,[2] 和 [3] 使用 seek,但 [1] 继续使用 scan。

为什么数据库不能使用带有 or 的索引?考虑到查询,该表的最佳索引方法是什么?

【问题讨论】:

这也取决于每个查询返回多少行。如果 (1) 旨在返回 50% 的表,那么扫描听起来不错,因为您也在使用 SELECT *,所以无论如何它都需要返回表以获取其他列 我的错误,我实际上并没有使用 *,我只是用它来简化代码。但是,我选择了所有列。会有这么大的不同吗? 使用SELECT *只是使用SELECT <list every column here>的捷径,所以参数是一样的 关于您的第一条评论,这意味着根据统计数据可能会或可能不会使用undex? 使用具有该行数的索引是没有意义的。只进行表扫描几乎总是更好(除非您使用的是SELECT [a column that's in the index]。表中的行太少 【参考方案1】:

使用两个单独的索引,一个在(email) 上,一个在(phone, email) 上。

OR 相当困难。如果您的条件是通过AND 而不是OR 连接的,那么您的索引将用于第一个查询(但不是第三个,因为phone 不是索引中的第一个键)。

您可以将查询编写为:

SELECT *
FROM Person 
WHERE Email = '...' 
UNION ALL
SELECT *
FROM Person 
WHERE Email <> '...' AND Phone = '...';

SQL Server 应该为每个子查询使用适当的索引。

【讨论】:

刚试过。仍在扫描 [...] WHERE Email = ... 另外,您能解释一下您选择索引的理由吗?此外,您的查询导致 2 次扫描 @victor 。 . .桌子有多大?除非数据跨越多个数据页,否则 SQL Server 不会使用索引。【参考方案2】:

为每列创建一个单独的索引。 通过使用提示,我们可以强制优化器使用/不使用索引,因此您可以检查执行计划,了解所涉及的性能并了解每条路径的含义。

浏览我的演示并考虑以下场景中每条路径所涉及的工作 -

    只有少数行满足条件 j=123。 只有少数行满足条件 k=456。

    大多数行满足条件 j=123。 大多数行满足条件 k=456。

    只有少数行满足条件 j=123。 大多数行满足条件 k=456。

试着想想你会为每个场景选择什么路径。 请随时提出问题。


演示

;with t(n) as (select 0 union all select n+1 from t where n < 999)

select      1+t0.n+1000*t1.n                                as i
           ,floor(rand(cast (newid() as varbinary))*1000)   as j
           ,floor(rand(cast (newid() as varbinary))*1000)   as k 

into        t

from        t t0,t t1 

option       (maxrecursion 0)
;

create index t_j on t (j);
create index t_k on t (k);

update statistics t (t_j)
update statistics t (t_k)

扫描

select      *
from        t (forcescan)
where       j = 123
        or  k = 456
这很简单。

寻找

select      *
from        t (forceseek)
where       j = 123
        or  k = 456
“Index Seek”:正在为每个索引寻找相关值(123 和 456) “合并连接”:结果(行 ID)正在被连接(如 UNION ALL 中) “流聚合”:正在消除重复的行 ID “Rid Lookup” + “Nested Loops”:行 ID 用于从表中检索行 (t)

【讨论】:

以上是关于SQL Server 为啥索引不与 OR 一起使用的主要内容,如果未能解决你的问题,请参考以下文章

为啥 SQL Server 不使用计算列上的索引?

为啥当 WHERE 子句包含参数化值时 SQL Server 使用索引扫描而不是索引查找

Sql server 创建索引后,只有查询后重建才会生效,不知为啥?

Zend条形码库不与Live Server中的codeigniter一起使用

序列生成器 Oracle 不与 import.sql 一起使用

Facebook 页脚栏是一个 iframe,那么为啥它不与页面的其余部分一起重新加载呢?