【问题标题】:SQL indexes for "not equal" searches“不等于”搜索的 SQL 索引
【发布时间】:2011-02-21 07:09:30
【问题描述】:

SQL 索引允许快速找到与我的查询匹配的字符串。现在,我必须在一个大表中搜索 not 匹配的字符串。当然,普通的索引也无济于事,我必须进行慢速顺序扫描:

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 当它看到顺序扫描时可以决定不使用索引 会更快(例如,如果几乎所有元组都匹配)。但, 在这里,“不相等”的搜索速度真的很慢。

有什么方法可以让这些“不等于”搜索更快?

这里是另一个例子,用来说明 Mark Byers 的出色言论。这 索引用于“=”查询(返回绝大多数 元组),但不适用于 '!=' 查询:

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)。

【问题讨论】:

    标签: sql postgresql indexing


    【解决方案1】:

    可能会有所帮助:

    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应该能够确定范围操作的选择性很高,并考虑为其使用索引。

    我不认为它可以直接使用索引来满足不等于谓词,尽管如果它可以在计划期间尝试重写上述不等于(如果有帮助的话)会很好。如果可行,请向开发人员推荐;)

    基本原理:在索引中搜索不等于某个值的所有值需要扫描整个索引。相比之下,搜索小于某个键的所有元素意味着在树中找到最大的不匹配项并向后扫描。类似地,以相反的方向搜索大于某个键的所有元素。使用 b-tree 结构很容易实现这些操作。此外,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'
    

    这将使&lt;&gt;-using select 语句只扫描该部分索引:因为它排除了表中的大部分条目,所以它应该很小。

    【讨论】:

    • 我刚刚测试了您将“”重写为“”的想法,并且它有效。 EXPLAIN 说明使用了索引,性能提升很多。我做更多的测试,我会接受你的回答。问题:为什么 PostgreSQL 不能自己重写?
    • @bortzmeyer 可能是因为操作员系统非常通用——它需要某种方式将“=”/“”对操作员与“”相关联。可能值得建议将 postgresql 列表作为一项功能。
    • 好的,它工作正常,谢谢。一个小警告:并非所有 PostgreSQL 索引都有排序 postgresql.org/docs/current/interactive/indexes-types.html
    • @bortzmeyer 刚刚意识到您也可以为此使用部分索引,请参阅更新。
    【解决方案2】:

    数据库可以使用索引进行此查询,但它选择不使用,因为它会更慢。 更新:这不太正确:您必须稍微重写查询。请参阅 Araqnid 的回答。

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

    如果您有不同类型的数据,其中大多数值是foo,只有少数是bar,而您说的是WHERE col &lt;&gt; 'foo',那么它可能会使用索引。

    有什么方法可以让这些“不等于”搜索更快?

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

    【讨论】:

    • 好吧,我一直忘记 DBMS 比我聪明,有时故意决定不使用索引。它甚至取决于查询中的实际值。但是,即使使用专门填充的数据库,我也无法使用索引进行 NOT 查询。即使只选择了 80 行,PostgreSQL 也会使用 Seq Scan。
    • 最后,我使用了araqnid的解决方案(重写了“”中的“”)并接受了他的解决方案。谢谢。
    猜你喜欢
    • 2014-11-12
    • 1970-01-01
    • 2010-10-31
    • 2015-05-31
    • 1970-01-01
    • 2011-03-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多