在PostgreSQL中对80M记录进行慢速正则表达式查询

时间:2017-07-28 17:09:05

标签: postgresql performance

我有一个包含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

如何更快地进行此查询?

任何想法都会非常感激。

P.S:

我正在使用PostgreSQL 9.6.1

更新

我刚刚更新了这个问题。我已停用Parallel Query并且结果已更改。

1 个答案:

答案 0 :(得分:1)

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

CREATE INDEX postal_code_idx  ON categorised_phones (postal_code varchar_pattern_ops) ;

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

| QUERY PLAN                                                                                                                             |
| :------------------------------------------------------------------------------------------------------------------------------------- |
| HashAggregate  (cost=2368.65..2368.67 rows=2 width=12) (actual time=18.093..18.094 rows=2 loops=1)                                     |
|   Group Key: operator                                                                                                                  |
|   ->  Bitmap Heap Scan on categorised_phones  (cost=536.79..2265.83 rows=20564 width=4) (actual time=2.564..12.061 rows=22171 loops=1) |
|         Filter: ((postal_code)::text ~~ '1%'::text)                                                                                    |
|         Heap Blocks: exact=1455                                                                                                        |
|         ->  Bitmap Index Scan on postal_code_idx  (cost=0.00..531.65 rows=21923 width=0) (actual time=2.386..2.386 rows=22171 loops=1) |
|               Index Cond: (((postal_code)::text ~>=~ '1'::text) AND ((postal_code)::text ~<~ '2'::text))                               |
| Planning time: 0.119 ms                                                                                                                |
| Execution time: 18.122 ms                                                                                                              |

您可以在 dbfiddle here

查看

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

<强>为什么吗

来自PostgreSQL documentation on operator classes

  

运算符类text_pattern_opsvarchar_pattern_opsbpchar_pattern_ops分别支持类型text,varchar和char上的B树索引。与默认运算符类的不同之处在于,这些值严格按字符进行比较,而不是根据特定于语言环境的排序规则进行比较。这使得当数据库不使用标准&#34; C&#34;语言环境。

来自PostgreSQL documentation on Index Types

  

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

<强>更新

如果执行的查询总是涉及修复(且相对较小)的LIKE 'x%'个表达式,请考虑使用partial indexes

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

CREATE INDEX idx_1 ON categorised_phones (operator) WHERE postal_code LIKE '1%';
VACUUM categorised_phones ;
| QUERY PLAN                                                                                                                                    |
| :-------------------------------------------------------------------------------------------------------------------------------------------- |
| GroupAggregate  (cost=0.29..658.74 rows=3 width=12) (actual time=3.235..6.493 rows=2 loops=1)                                                 |
|   Group Key: operator                                                                                                                         |
|   ->  Index Only Scan using idx_1 on categorised_phones  (cost=0.29..554.10 rows=20921 width=4) (actual time=0.028..3.266 rows=22290 loops=1) |
|         Heap Fetches: 0                                                                                                                       |
| Planning time: 0.293 ms                                                                                                                       |
| Execution time: 6.517 ms                                                                                                                      |