“不等于”搜索的SQL索引

时间:2010-05-19 09:30:34

标签: sql postgresql indexing

SQL索引允许快速查找与我的查询匹配的字符串。现在,我必须在一张大表中搜索<​​strong>不匹配的字符串。当然,正常索引没有帮助,我必须进行慢速顺序扫描:

essais=> \d phone_idx
Index "public.phone_idx"
 Column | Type 
--------+------
 phone  | text
btree, for table "public.phonespersons"

essais=> EXPLAIN SELECT person FROM PhonesPersons WHERE phone = '+33 1234567';
                                  QUERY PLAN                                   
-------------------------------------------------------------------------------
 Index Scan using phone_idx on phonespersons  (cost=0.00..8.41 rows=1 width=4)
   Index Cond: (phone = '+33 1234567'::text)
(2 rows)

essais=> EXPLAIN SELECT person FROM PhonesPersons WHERE phone != '+33 1234567';
                              QUERY PLAN                              
----------------------------------------------------------------------
 Seq Scan on phonespersons  (cost=0.00..18621.00 rows=999999 width=4)
   Filter: (phone <> '+33 1234567'::text)
(2 rows)

我理解(参见Mark Byers的非常好的解释)PostgreSQL 可以在看到顺序扫描时决定不使用索引 会更快(例如,如果几乎所有的元组匹配)。但, 在这里,“不相等”的搜索真的很慢。

任何使这些“不等于”的方法更快搜索?

这是另一个例子,以解决马克拜尔斯的优秀言论。该 index用于'='查询(返回绝大多数 元组)但不是'!='查询:

essais=> \d tld_idx
 Index "public.tld_idx"
     Column      | Type 
-----------------+------
 pg_expression_1 | text
btree, for table "public.emailspersons"

essais=> EXPLAIN ANALYZE SELECT person FROM EmailsPersons WHERE tld(email) = 'fr';
                             QUERY PLAN                                                             
------------------------------------------------------------------------------------------------------------------------------------
 Index Scan using tld_idx on emailspersons  (cost=0.25..4010.79 rows=97033 width=4) (actual time=0.137..261.123 rows=97110 loops=1)
   Index Cond: (tld(email) = 'fr'::text)
 Total runtime: 444.800 ms
(3 rows)

essais=> EXPLAIN ANALYZE SELECT person FROM EmailsPersons WHERE tld(email) != 'fr';
                         QUERY PLAN                                                     
--------------------------------------------------------------------------------------------------------------------
 Seq Scan on emailspersons  (cost=0.00..27129.00 rows=2967 width=4) (actual time=1.004..1031.224 rows=2890 loops=1)
   Filter: (tld(email) <> 'fr'::text)
 Total runtime: 1037.278 ms
(3 rows)

DBMS是PostgreSQL 8.3(但我可以升级到8.4)。

2 个答案:

答案 0 :(得分:5)

数据库能够使用此查询的索引,但它选择不这样做,因为它会更慢。 更新:这不太正确:您必须稍微重写查询。见Araqnid的答案。

您的where子句几乎选择表中的所有行(rows = 999999)。在这种情况下,数据库可以看到表扫描更快,因此忽略索引。它更快,因为列person不在索引中,所以它必须为每一行进行两次查找,一次在索引中检查WHERE子句,然后在主表中再次获取列{ {1}}。

如果你有一个不同类型的数据,其中大多数值是person,只有少数是foo而你说的是bar,那么它可能会使用索引。

  

任何使这些“不等于”的方法更快搜索?

任何选择近100万行的查询都会很慢。尝试添加限制条款。

答案 1 :(得分:4)

可能会有所帮助:

SELECT person FROM PhonesPersons WHERE phone < '+33 1234567'
UNION ALL
SELECT person FROM PhonesPersons WHERE phone > '+33 1234567'

或只是

SELECT person FROM PhonesPersons WHERE phone > '+33 1234567'
                                       OR phone < '+33 1234567'

PostgreSQL应该能够确定范围操作的选择性非常高,并考虑使用它的索引。

我认为它不能直接使用索引来满足not-equals谓词,尽管如果它可以尝试在规划期间重写上面的not-equals(如果有帮助的话)会很好。如果有效,请向开发人员提出建议;)

基本原理:搜索索引以查找不等于某个值的所有值需要扫描完整索引。相反,搜索少于某个键的所有元素意味着在树中找到最大的非匹配项并向后扫描。类似地,在相反方向上搜索大于某个键的所有元素。使用b树结构很容易实现这些操作。此外,PostgreSQL收集的统计数据应该能够指出“+33 1234567”是一个已知的频繁值:通过从1中删除那些和空值的频率,我们有剩余的行选择比例:直方图边界将表明这些是否偏向一侧。但是如果排除空值和频繁值推动行的比例保持足够低(Istr约20%),则索引扫描应该是合适的。检查pg_stats中列的统计信息,以查看实际计算的比例。

更新:我在一个分布模糊的本地表上尝试了这个,并且上面的两种形式都产生了除普通seq扫描之外的其他内容。后者(使用“OR”)是一个位图扫描,如果对你的共同值的偏差特别极端,它实际上可能只是一个seq扫描...虽然规划者可以看到,我认为它不会自动在内部重写为“附加(索引扫描,索引扫描)”。关闭“enable_bitmapscan”只会使其恢复为seq扫描。

PS :索引文本列并使用不等式运算符可能是一个问题,如果您的数据库位置不是C.您可能需要添加一个使用text_pattern_ops或varchar_pattern_ops的额外索引;这类似于column LIKE 'prefix%'谓词的索引问题。

替代:您可以创建部分索引:

CREATE INDEX PhonesPersonsOthers ON PhonesPersons(phone) WHERE phone <> '+33 1234567'

这将使<> - 使用select语句只扫描该部分索引:因为它排除了表中的大多数条目,所以它应该很小。