我需要哪些索引来加速AND / OR SQL查询

时间:2014-11-28 21:21:40

标签: sql database postgresql indexing

假设我有一个名为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有多聪明,以及这些索引是否正确

2 个答案:

答案 0 :(得分:1)

你的方法是合理的。这里有两个因素是必不可少的:

  1. Postgres可以非常有效地将多个索引与位图索引扫描相结合。

  2. 当涉及索引的列时,B树索引的使用效果最为有效。

  3. 测试用例

    如果你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 ......

    列的顺序对于2列索引customer_age_name_idxcustomer_age_lastname_idx无关紧要。细节和测试用例:

    我仍然首先将age与我建议的第3个索引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查询替换。无论如何,它会更快。

答案 1 :(得分:0)

将OR转换为两个查询UNIONed:

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

然后在(年龄,名字)和(年龄,姓氏)之上创建一个索引。