【问题标题】:What indexes do I need to speed up AND/OR SQL queries我需要哪些索引来加速 AND/OR SQL 查询
【发布时间】:2014-11-28 21:21:39
【问题描述】:

假设我有一个名为 customer 的表,如下所示:

+----+------+----------+-----+
| id | name | lastname | age |
+----+------+----------+-----+
| .. | ...  |   ....   | ... |

我需要执行以下查询:

SELECT * FROM customer WHERE ((name = 'john' OR lastname = 'doe') AND age = 21)

我知道单列索引和多列索引是如何工作的,所以我创建了这些索引:

(name, age)
(lastname, age)

这就是我需要的所有索引吗?

上述条件可以改写为:

... WHERE ((name = 'john' AND age = 21) OR (lastname = 'doe' AND age = 21)

但我不确定 RDBMS 有多智能,以及这些索引是否正确

【问题讨论】:

  • 为什么不对查询执行一些诊断? IE。如果您使用的是 SQL Server,请使用执行计划并查看索引是否节省成本。
  • 没有将整个表放入索引中,我认为该查询无法进行太多优化
  • @Mihai 我的表有比这更多的字段......这只是一个例子。
  • @DarrenDavies 不幸的是,我没有足够的数据来测量测试......而且我不知道有任何工具可以告诉我当前是否在我的数据库模式中工作。
  • 有关索引或性能的问题可能取决于您的 Postgres 版本。 始终提供。

标签: sql database postgresql indexing


【解决方案1】:

你的方法是合理的。这里有两个重要因素:

  1. Postgres 可以通过位图索引扫描非常有效地组合多个索引。

  2. 只涉及索引的前导列时,B-tree 索引的使用是迄今为止最有效的。

测试用例

如果你don't have enough data to measure tests,你总是可以像这样快速创建一个测试用例:

CREATE TABLE customer (id int, name text, lastname text, age int);

INSERT INTO customer
SELECT g
     , left(md5('foo'::text || g%500) , 3 + ((g%5)^2)::int)
     , left(md5('bar'::text || g%1000), 5 + ((g%5)^2)::int)
     , ((random()^2) * 100)::int
FROM   generate_series(1, 30000) g; -- 30k rows for quick test case

对于您的查询(重新格式化):

SELECT *
FROM   customer
WHERE (name = 'john' OR lastname = 'doe')
AND    age = 21;

我会去

CREATE INDEX customer_age_name_idx ON customer (age, name);
CREATE INDEX customer_age_lastname_idx ON customer (age, lastname);

但是,根据许多因素,一个单个索引包含所有三列和年龄作为第一个可能能够提供类似的性能。经验法则是创建尽可能少的索引和尽可能多的索引。

CREATE INDEX customer_age_lastname_name_idx ON customer (age, lastname, name);

在这种情况下,对(age, name) 的检查可能会更慢,但取决于第一列的选择性,这可能并不重要。

Updated SQL Fiddle.

为什么age 在索引中排在第一位?

这不是很重要,需要更深入的理解来解释。但是since you ask ...

对于两列索引customer_age_name_idxcustomer_age_lastname_idx,列的顺序无关紧要。详细信息和测试用例:

我仍然将age 放在第一位,以与我建议的第三个索引customer_age_lastname_name_idx 保持一致,其中列的顺序确实很重要在多个方面:

最重要的是,您的谓词(age, name)(age, lastname) 共享age 列。 B 树索引(到目前为止)在前导列上最有效,因此将 age 放在首位对两者都有好处。

而且,不太重要但仍然相关:由于索引页面的数据类型特征、对齐方式、填充和页面布局,索引的大小以这种方式更小。

age 是一个 4 字节的 integer,并且必须在数据页中以 4 字节的倍数对齐。 text 是可变长度的,没有对齐限制。由于“列俄罗斯方块”的规则,将整数放在第一个或最后一个更有效。我在(lastname, age, name)(中间的age!)上添加了另一个索引,只是为了证明它大了~10%。额外填充不会丢失任何空间,从而导致索引更小。 尺寸很重要

出于同样的原因,最好像这样重新排列演示表中的列(id, age, name, lastname)。如果您想了解原因,请从这里开始:

我写的一切都是为了手头的案子。如果您有其他查询/其他要求,最终的策略可能会发生变化。

UNION 查询等效?

请注意,UNION 查询可能也可能不返回相同的结果。它折叠重复的行,而您的原始行没有。即使您的表中没有完整 个重复项,您仍可能会在 SELECT 列表中的一部分列中看到这种效果。不要盲目地用UNION 查询代替。无论如何,它不会更快。

【讨论】:

  • 非常感谢您的回复 ;) 我有一个简单的问题……在我的查询中,(age, name)(name, age) 有什么关系?
  • @OscarMederos:嗯,2 列索引并不重要。但这对于 3 列索引很重要,对于基础表也很重要。
【解决方案2】:

将 OR 变成两个 UNIONed 查询:

SELECT * FROM Customer WHERE Age = 21 AND Name = 'John'
UNION
SELECT * FROM Customer WHERE Age = 21 AND LastName = 'Doe'

然后创建一个基于 (Age, Name) 的索引和另一个基于 (Age, LastName) 的索引。

【讨论】:

  • 感谢您的回复。我知道我可以做到,但这不完全是我的问题......
  • 是的,这些是索引。但建议使用 UNION 而不是 OR。
  • @RicardoPeres:这个“推荐”对我来说是新闻。谁推荐的?
  • 您的 1st link 用于 Access 2007 (!),您的 2nd3rd 用于 SQL Server。它们都解决了各自 MS 产品的实现细节,与 Postgres(或其他 SQL 实现)无关。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-03-25
  • 2017-08-03
  • 2016-12-22
  • 1970-01-01
  • 2013-06-22
  • 2011-01-04
  • 2016-10-05
相关资源
最近更新 更多