假设我有一个名为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有多聪明,以及这些索引是否正确
答案 0 :(得分:1)
你的方法是合理的。这里有两个因素是必不可少的:
Postgres可以非常有效地将多个索引与位图索引扫描相结合。
当涉及索引的仅列时,B树索引的使用效果最为有效。
如果你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)
的检查可能会更慢,但根据第一列的选择性,它可能并不重要。
age
首先在索引中?这不是很重要,需要更深入的理解来解释。但是since you ask ......
列的顺序对于2列索引customer_age_name_idx
和customer_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'
然后在(年龄,名字)和(年龄,姓氏)之上创建一个索引。