POSTGRESQL(PG) 性能优化之like全文检索优化

使用like操作可以进行字符串比较,全文检索等,性能相对比较差,有些情况下可以通过建立索引来提升性能。下面我们通过使用TPCH orders表作为例子,来进行说明。但是请注意, not like是不能用任何索引的,BTREE不支持!=操作,只能进行=和范围查找。

TPCH orders表的定义如下,其中o_comment列是varchar类型的字符串:

tpch10=# \d orders
                          Table "public.orders"
     Column      |         Type          | Collation | Nullable | Default
-----------------+-----------------------+-----------+----------+---------
 o_orderkey      | integer               |           | not null |
 o_custkey       | integer               |           | not null |
 o_orderstatus   | character(1)          |           | not null |
 o_totalprice    | numeric(15,2)         |           | not null |
 o_orderdate     | date                  |           | not null |
 o_orderpriority | character(15)         |           | not null |
 o_clerk         | character(15)         |           | not null |
 o_shippriority  | integer               |           | not null |
 o_comment       | character varying(79) |           | not null |
Indexes:
    "orders_pkey" PRIMARY KEY, btree (o_orderkey)
Foreign-key constraints:
    "orders_o_custkey_fkey" FOREIGN KEY (o_custkey) REFERENCES customer(c_custkey)
Referenced by:
    TABLE "lineitem" CONSTRAINT "lineitem_l_orderkey_fkey" FOREIGN KEY (l_orderkey) REFERENCES orders(o_orderkey)

1. 前匹配 LIKE ‘prefix%’范围查询

前匹配可以使用BTREE索引进行范围查询,在没有任何索引的情况下,PG默认使用seqscan顺序扫描:

tpch10=# explain analyze select * from orders where o_comment like 'request%';
                                                    QUERY PLAN
------------------------------------------------------------------------------------------------------------------
 Seq Scan on orders  (cost=0.00..448412.00 rows=75000 width=234) (actual time=0.662..5098.904 rows=56870 loops=1)
   Filter: ((o_comment)::text ~~ 'request%'::text)
   Rows Removed by Filter: 14943130
 Planning Time: 0.268 ms
 Execution Time: 5103.280 ms
(5 rows)

我们在o_comment列加一下BTREE索引,然后再次尝试:

tpch10=# create index on orders(o_comment);
CREATE INDEX
tpch10=# analyze orders;
ANALYZE
tpch10=# explain analyze select * from orders where o_comment like 'request%';
                                                    QUERY PLAN
------------------------------------------------------------------------------------------------------------------
 Seq Scan on orders  (cost=0.00..448412.00 rows=75000 width=234) (actual time=0.662..5098.904 rows=56870 loops=1)
   Filter: ((o_comment)::text ~~ 'request%'::text)
   Rows Removed by Filter: 14943130
 Planning Time: 0.268 ms
 Execution Time: 5103.280 ms
(5 rows)
tpch10=# explain analyze select * from orders where o_comment like 'request';
                                                          QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------
 Index Scan using orders_o_comment_idx on orders  (cost=0.56..8.58 rows=1 width=107) (actual time=0.188..0.188 rows=0 loops=1)
   Index Cond: ((o_comment)::text = 'request'::text)
   Filter: ((o_comment)::text ~~ 'request'::text)
 Planning Time: 0.408 ms
 Execution Time: 0.250 ms
(5 rows)

发现PG在like ‘prefix%’并没有使用BTREE索引,而精确匹配可以使用索引,问题在于默认的collate是en_US.UTF-8,而不是C,将o_comment列改为C collate再进行尝试。使用alter table命令将o_comment列改成collate C,可以查看到o_comment列的Collation属性是C。

tpch10=# alter table orders alter column o_comment TYPE VARCHAR(79) collate "C";
ALTER TABLE
tpch10=# \d orders
                          Table "public.orders"
     Column      |         Type          | Collation | Nullable | Default
-----------------+-----------------------+-----------+----------+---------
 o_orderkey      | integer               |           | not null |
 o_custkey       | integer               |           | not null |
 o_orderstatus   | character(1)          |           | not null |
 o_totalprice    | numeric(15,2)         |           | not null |
 o_orderdate     | date                  |           | not null |
 o_orderpriority | character(15)         |           | not null |
 o_clerk         | character(15)         |           | not null |
 o_shippriority  | integer               |           | not null |
 o_comment       | character varying(79) | C         | not null |
Indexes:
    "orders_pkey" PRIMARY KEY, btree (o_orderkey)
    "orders_o_comment_idx" btree (o_comment)
Foreign-key constraints:
    "orders_o_custkey_fkey" FOREIGN KEY (o_custkey) REFERENCES customer(c_custkey)
Referenced by:
    TABLE "lineitem" CONSTRAINT "lineitem_l_orderkey_fkey" FOREIGN KEY (l_orderkey) REFERENCES orders(o_orderkey)

tpch10=#

再执行一次之前的语句,发现使用了创建的BTREE索引,时间从5103ms提升到232ms,可以看到性能得到极大提升。

tpch10=# explain analyze select * from orders where o_comment like 'request%';
                                                                QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on orders  (cost=3477.31..168092.77 rows=75000 width=234) (actual time=50.869..228.043 rows=56870 loops=1)
   Filter: ((o_comment)::text ~~ 'request%'::text)
   Heap Blocks: exact=51160
   ->  Bitmap Index Scan on orders_o_comment_idx  (cost=0.00..3458.56 rows=75000 width=0) (actual time=33.213..33.213 rows=56870 loops=1)
         Index Cond: (((o_comment)::text >= 'request'::text) AND ((o_comment)::text < 'requesu'::text))
 Planning Time: 0.528 ms
 Execution Time: 232.989 ms
(7 rows)

tpch10=#

2. 后匹配 LIKE ‘%prefix’范围查询

后匹配的方式不能使用BTREE索引,原因在于BTREE索引只能做大于、大于等于、等于、小于、小于等于等操作决定的。因此这时候使用reverse index来处理后匹配的文本检索。

比如我们使用相同的例子,只是like匹配的是‘%request’,PG依旧使用顺序扫描,而没有使用之前建的索引。

tpch10=# explain analyze select * from orders where o_comment like '%request';
                                                    QUERY PLAN
-------------------------------------------------------------------------------------------------------------------
 Seq Scan on orders  (cost=0.00..448412.00 rows=151515 width=107) (actual time=0.043..8640.280 rows=57305 loops=1)
   Filter: ((o_comment)::text ~~ '%request'::text)
   Rows Removed by Filter: 14942695
 Planning Time: 1.065 ms
 Execution Time: 8645.223 ms
(5 rows)

我们给o_comment列建一个reverse的索引,会看到执行语句会选用reverse索引,时间由8645ms降到162ms,提升效率非常高。因此在这种情况下可以考虑使用reverse索引来提高后匹配的检索效率。

tpch10=# create index on orders(reverse(o_comment));
CREATE INDEX
tpch10=# analyze orders;
ANALYZE

tpch10=# explain analyze select * from orders where reverse(o_comment) like reverse('%request');
                                                              QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------
 Index Scan using orders_reverse_idx on orders  (cost=0.56..8.59 rows=1500 width=107) (actual time=0.142..157.412 rows=57305 loops=1)
   Index Cond: ((reverse((o_comment)::text) >= 'tseuqer'::text) AND (reverse((o_comment)::text) < 'tseuqes'::text))
   Filter: (reverse((o_comment)::text) ~~ 'tseuqer%'::text)
 Planning Time: 0.620 ms
 Execution Time: 162.048 ms
(5 rows)

tpch10=#

3. 中间匹配'%prefix%'范围查询

对于中间匹配的全文检索,以上两种索引都不能解决,PG提供了GIN索引,可以使用PG提供的pg_trgm插件来加速查询。GIN索引可以应用于前匹配、后匹配和中间匹配,但是因为GIN本身比较昂贵,对于前匹配和后匹配,可以使用上面提到的方式,中间匹配采用GIN索引,但是也需要考虑索引维护本身的代价。

在没有使用GIN索引时候,PG依旧使用顺序扫描:

tpch10=# explain analyze select * from orders where o_comment like '%request%';
                                                      QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
 Seq Scan on orders  (cost=0.00..448442.18 rows=2273093 width=107) (actual time=0.108..8773.358 rows=2318296 loops=1)
   Filter: ((o_comment)::text ~~ '%request%'::text)
   Rows Removed by Filter: 12681704
 Planning Time: 0.836 ms
 Execution Time: 8901.078 ms
(5 rows)

tpch10=# explain analyze select * from orders where reverse(o_comment) like reverse('%request%');
                                                      QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------
 Seq Scan on orders  (cost=0.00..485948.21 rows=3030791 width=107) (actual time=0.069..18095.022 rows=2318296 loops=1)
   Filter: (reverse((o_comment)::text) ~~ '%tseuqer%'::text)
   Rows Removed by Filter: 12681704
 Planning Time: 4.783 ms
 Execution Time: 18218.893 ms
(5 rows)

但是GIN索引的创建和维护非常昂贵,因为GIN索引对每组三个连续字符(三元组)进行索引,以便能够查找任何匹配模式。而且GIN索引也可以应用于前后匹配的组合使用,这种情况下效率也很高。

加载pg_trgm插件后,在o_comment列创建GIN索引,重新进行查询,时间由8901ms提升到3939ms,效率得到了提升,同时我们也可以看到GIN索引也可以进行‘%prefix%prefix%’的查询,效率也是很明显的提升。

tpch10=# create extension if not exists pg_trgm;
CREATE EXTENSION
tpch10=# create index on orders using gin (o_comment gin_trgm_ops);
CREATE INDEX
tpch10=# analyze orders;
ANALYZE

tpch10=# explain analyze select * from orders where o_comment like '%request%';
                                                                     QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on orders  (cost=29361.97..330044.89 rows=3181674 width=107) (actual time=1727.012..3828.371 rows=2318296 loops=1)
   Recheck Cond: ((o_comment)::text ~~ '%request%'::text)
   Rows Removed by Index Recheck: 2088
   Heap Blocks: exact=260892
   ->  Bitmap Index Scan on orders_o_comment_idx1  (cost=0.00..28566.55 rows=3181674 width=0) (actual time=1604.355..1604.355 rows=2320384 loops=1)
         Index Cond: ((o_comment)::text ~~ '%request%'::text)
 Planning Time: 0.473 ms
 Execution Time: 3939.557 ms
(8 rows)


tpch10=# explain analyze select * from orders where o_comment like '%special%request%';
                                                                   QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on orders  (cost=1478.19..236736.24 rows=151508 width=107) (actual time=1794.711..2266.371 rows=168338 loops=1)
   Recheck Cond: ((o_comment)::text ~~ '%special%request%'::text)
   Rows Removed by Index Recheck: 80603
   Heap Blocks: exact=161167
   ->  Bitmap Index Scan on orders_o_comment_idx1  (cost=0.00..1440.31 rows=151508 width=0) (actual time=1731.915..1731.915 rows=248941 loops=1)
         Index Cond: ((o_comment)::text ~~ '%special%request%'::text)
 Planning Time: 0.501 ms
 Execution Time: 2278.128 ms
(8 rows)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

helenbi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值