【问题标题】:Slow regex query on 80M record in PostgreSQL在 PostgreSQL 中对 80M 记录进行慢正则表达式查询
【发布时间】:2017-07-29 13:21:14
【问题描述】:

我有一个包含 8000 万行的只读表:

   Column    |          Type          | Modifiers | Storage  | Stats target | Description 
-------------+------------------------+-----------+----------+--------------+-------------
 id          | character(11)          | not null  | extended |              | 
 gender      | character(1)           |           | extended |              | 
 postal_code | character varying(10)  |           | extended |              | 
 operator    | character varying(5)   |           | extended |              | 

Indexes:
    "categorised_phones_pkey" PRIMARY KEY, btree (id)
    "operator_idx" btree (operator)
    "postal_code_trgm_idx" gin (postal_code gin_trgm_ops)

id 是主键,包含唯一的手机号码。表格行如下所示:

      id        |     gender   |   postal_code  |   operator
----------------+--------------+----------------+------------
 09567849087    |      m       |   7414776788   |     mtn
 09565649846    |      f       |   1268398732   |     mci
 09568831245    |      f       |   7412556443   |     mtn
 09469774390    |      m       |   5488312790   |     mci

此查询第一次大约需要约 65 秒,下次需要约 8 秒:

select operator,count(*) from categorised_phones where postal_code like '1%' group by operator;

输出如下所示:

operator |  count  
----------+---------
 mci      | 4050314
 mtn      | 6235778

还有explain alanyze的输出:

HashAggregate  (cost=1364980.61..1364980.63 rows=2 width=10) (actual time=8257.026..8257.026 rows=2 loops=1)
   Group Key: operator
   ->  Bitmap Heap Scan on categorised_phones  (cost=95969.17..1312915.34 rows=10413054 width=2) (actual time=1140.803..6332.534 rows=10286092 loops=1)
         Recheck Cond: ((postal_code)::text ~~ '1%'::text)
         Rows Removed by Index Recheck: 25105697
         Heap Blocks: exact=50449 lossy=237243
         ->  Bitmap Index Scan on postal_code_trgm_idx  (cost=0.00..93365.90 rows=10413054 width=0) (actual time=1129.270..1129.270 rows=10287127 loops=1)
               Index Cond: ((postal_code)::text ~~ '1%'::text)
 Planning time: 0.540 ms
 Execution time: 8257.392 ms

我怎样才能使这个查询更快?

任何想法将不胜感激。

附注:

我使用的是 PostgreSQL 9.6.1

更新

我刚刚更新了问题。我禁用了Parallel Query,结果发生了变化。

【问题讨论】:

  • 您在postal_code 上有一个杜松子酒索引。为它定义了哪些操作?您似乎没有使用任何会提示使用该索引的东西。
  • 我正在使用LIKE '1%'的杜松子酒索引

标签: postgresql performance


【解决方案1】:

对于涉及LIKE '%start' 形式比较的查询,并遵循 PostgreSQL 自己的建议,您可以使用以下索引:

CREATE INDEX postal_code_idx  ON categorised_phones (postal_code varchar_pattern_ops) ;

有了该索引和一些模拟数据,您的执行计划很可能如下所示:

|查询计划 | | :------------------------------------------------ -------------------------------------------------- ---------------------------------- | | HashAggregate (cost=2368.65..2368.67 rows=2 width=12) (实际时间=18.093..18.094 rows=2 loops=1) | |组键:操作员 | | -> categorised_phones 上的位图堆扫描(成本=536.79..2265.83 行=20564 宽度=4)(实际时间=2.564..12.061 行=22171 循环=1)| |过滤器: ((postal_code)::text ~~ '1%'::text) | |堆块:精确=1455 | | -> postal_code_idx 上的位图索引扫描(成本=0.00..531.65 行=21923 宽度=0)(实际时间=2.386..2.386 行=22171 循环=1)| |索引条件: (((postal_code)::text ~>=~ '1'::text) AND ((postal_code)::text ~

您可以在 dbfiddle here

上查看

如果您对LIKE 'start%'LIKE '%middle%'两个 查询,则应添加此索引,但保留已存在的索引。三元组索引可能证明对第二种匹配很有用。


为什么?

来自PostgreSQL documentation on operator classes

运算符类 text_pattern_opsvarchar_pattern_opsbpchar_pattern_ops 分别支持 text、varchar 和 char 类型的 B 树索引。与默认运算符类的区别在于,这些值是严格逐个字符比较的,而不是根据特定于语言环境的排序规则。当数据库不使用标准“C”语言环境时,这使得这些运算符类适用于涉及模式匹配表达式(LIKE 或 POSIX 正则表达式)的查询。

来自PostgreSQL documentation on Index Types

如果模式是常量并且锚定到字符串的开头,优化器还可以使用 B-tree 索引进行涉及模式匹配运算符 LIKE~ 的查询 - 例如,col LIKE 'foo%'col ~ '^foo',但不是 LIKE '%bar'。但是,如果您的数据库不使用 C 语言环境,您将需要使用特殊的运算符类创建索引以支持模式匹配查询的索引;见下文第 11.9 节。 ILIKE~* 也可以使用 B-tree 索引,但前提是模式以非字母字符开头,即不受大小写转换影响的字符。


更新

如果执行的查询始终涉及固定数量(且相对较少)的LIKE 'x%' 表达式,请考虑使用partial indexes

例如,对于LIKE '1%',您将拥有以下索引和以下查询计划(它显示了大约 3 倍的改进):

CREATE INDEX idx_1 ON categorised_phones (operator) WHERE postal_code LIKE '1%';
VACUUM categorised_phones ;
|查询计划 | | :------------------------------------------------ -------------------------------------------------- ------------------------------------------ | | GroupAggregate(成本=0.29..658.74 行=3 宽度=12)(实际时间=3.235..6.493 行=2 循环=1)| |组键:操作员 | | -> Index Only Scan using idx_1 on categorised_phones (cost=0.29..554.10 rows=20921 width=4) (实际时间=0.028..3.266 rows=22290 loops=1) | |堆取数:0 | |规划时间:0.293 ms | |执行时间:6.517 ms |

【讨论】:

  • 我只有LIKE 'start%'
  • 然后删除“postal_code_trgm_idx”并创建我建议的那个。并尝试看看有什么不同。
  • 添加索引后,第一次查询大约需要 65 秒。之后查询大约需要 7-8 秒
  • 你已经缓存了所有的索引......那么它真的很快。 80 M 行是一个不错的数字,特别是如果您的数据库需要计算其中很大一部分(例如大约 10%)。确保您分析表格以获得良好的统计数据。
  • 顺便说一下,gin_trgm_ops 第一次也需要 60-70 秒,而下一次查询大约需要 8 秒。我认为您的建议使结果时间更好一点!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-09-17
  • 2018-09-04
  • 2018-08-04
  • 2013-08-27
  • 2010-09-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多