当前位置:   article > 正文

PostgreSQL 索引虚拟列 - 表达式索引 - JOIN提速

pgsql设置虚列索引

标签

PostgreSQL , join , 表达式索引 , 虚拟列索引 , 静态数据 , immutable函数


背景

CASE: 使用虚拟索引,响应时间从2.3秒下降到0.3毫秒

业务系统在设计时,为了减少数据冗余,提升可读性,通常需要将不同的数据放到不同的表。

在查询时,通过多表JOIN来补齐需要查询或在过滤的内容。

比如这样的例子:

有两张表,分别有1千万和100万数据,当用户查询时,需要补齐那100万表中的某个字段进行过滤。

  1. create table a (id int, bid int, c1 int, c2 int, c3 int);
  2. CREATE TABLE b (id int primary key, path text);
  3. insert into a select id, random()*1000000 , random()*10000000, random()*10000000 , random()*10000000 from generate_series(1,10000000) t(id);
  4. insert into b select id, md5(random()::text) from generate_series(1,1000000) t(id);
  5. create index idx_b_1 on b(path text_pattern_ops);
  6. -- 查询
  7. select a.* from a left join b on (a.bid=b.id and b.path like 'abc%');

那么它的性能如何呢?

  1. postgres=# explain select a.* from a left join b on (a.bid=b.id) where b.path like 'abcde%';
  2. QUERY PLAN
  3. ------------------------------------------------------------------------------------
  4. Hash Join (cost=9.70..289954.61 rows=1000 width=20)
  5. Hash Cond: (a.bid = b.id)
  6. -> Seq Scan on a (cost=0.00..163695.00 rows=10000000 width=20)
  7. -> Hash (cost=8.45..8.45 rows=100 width=4)
  8. -> Index Scan using idx_b_1 on b (cost=0.42..8.45 rows=100 width=4)
  9. Index Cond: ((path ~>=~ 'abcde'::text) AND (path ~<~ 'abcdf'::text))
  10. Filter: (path ~~ 'abcde%'::text)
  11. (7 rows)
  12. Time: 0.777 ms
  13. postgres=# select a.* from a left join b on (a.bid=b.id) where b.path like 'abcde%';
  14. id | bid | c1 | c2 | c3
  15. ---------+--------+---------+---------+---------
  16. 2423577 | 633740 | 846719 | 1720744 | 416608
  17. 2433286 | 633740 | 9797626 | 6737349 | 5669893
  18. 3851817 | 633740 | 8764393 | 3779499 | 2830950
  19. 4889541 | 633740 | 3892055 | 9470525 | 611262
  20. 5004634 | 633740 | 5420943 | 2448245 | 5719976
  21. 5372019 | 633740 | 5402891 | 3441462 | 8194368
  22. 6051251 | 633740 | 8691218 | 7184625 | 5940346
  23. 6344344 | 633740 | 5869018 | 9352883 | 636112
  24. 9751456 | 633740 | 3797867 | 1934900 | 2511398
  25. (9 rows)
  26. Time: 2348.506 ms (00:02.349)

条件越多,性能会越差。

这样的查询,并发一高,性能影响会比较大。

当b表是静态的时(没有DML),可以用虚拟列索引来实现优化。

表达式索引 - 虚拟列索引

假设B表不会发生DML,是一个静态表。

1、创建一个获取path的函数

  1. create or replace function get_path(int) returns text as $$
  2. select path from b where id=$1;
  3. $$ language sql strict immutable;

这个函数用于从B表获取path,假设B表静态(不会有增删改),那么这个函数就是immutable的,无论什么时候输入一个ID,返回的都是同一个path。

2、在a表直接创建表达式索引(虚拟列索引)

create index idx_a_1 on a(get_path(bid) text_pattern_ops);  

3、修改SQL语句如下

select * from a where get_path(bid) like 'abc%';  

4、响应时间从2.3秒下降到0.3毫秒。

  1. postgres=# select * from a where get_path(bid) like 'abcde%';
  2. id | bid | c1 | c2 | c3
  3. ---------+--------+---------+---------+---------
  4. 2423577 | 633740 | 846719 | 1720744 | 416608
  5. 2433286 | 633740 | 9797626 | 6737349 | 5669893
  6. 3851817 | 633740 | 8764393 | 3779499 | 2830950
  7. 4889541 | 633740 | 3892055 | 9470525 | 611262
  8. 5004634 | 633740 | 5420943 | 2448245 | 5719976
  9. 5372019 | 633740 | 5402891 | 3441462 | 8194368
  10. 6051251 | 633740 | 8691218 | 7184625 | 5940346
  11. 6344344 | 633740 | 5869018 | 9352883 | 636112
  12. 9751456 | 633740 | 3797867 | 1934900 | 2511398
  13. (9 rows)
  14. postgres=# select * from b where path like 'abcde%';
  15. id | path
  16. --------+----------------------------------
  17. 633740 | abcde980c8568a9a6a140885d92fcebe
  18. (1 row)
  19. postgres=# explain (analyze,verbose,timing,costs,buffers) select * from a where get_path(bid) like 'abcde%';
  20. QUERY PLAN
  21. ----------------------------------------------------------------------------------------------------------------------------
  22. Index Scan using idx_a_1 on public.a (cost=0.56..158697.56 rows=50000 width=20) (actual time=0.151..0.276 rows=9 loops=1)
  23. Output: id, bid, c1, c2, c3
  24. Index Cond: ((get_path(a.bid) ~>=~ 'abcde'::text) AND (get_path(a.bid) ~<~ 'abcdf'::text))
  25. Filter: (get_path(a.bid) ~~ 'abcde%'::text)
  26. Buffers: shared hit=50
  27. Planning time: 0.092 ms
  28. Execution time: 0.300 ms
  29. (7 rows)

即使没有虚拟索引,也可以PostgreSQL的使用并行计算

32个并行,耗时454毫秒,依旧不如使用虚拟列索引的效果。

  1. postgres=# set parallel_tuple_cost =0;
  2. SET
  3. postgres=# set parallel_setup_cost =0;
  4. SET
  5. postgres=# set max_parallel_workers_per_gather =32;
  6. SET
  7. postgres=# alter table a set (parallel_workers =32);
  8. ALTER TABLE
  9. postgres=# explain select a.* from a left join b on (a.bid=b.id) where b.path like 'abcde%';
  10. QUERY PLAN
  11. --------------------------------------------------------------------------------
  12. Gather (cost=20835.25..91600.56 rows=1000 width=20)
  13. Workers Planned: 32
  14. -> Hash Join (cost=20835.25..91600.56 rows=31 width=20)
  15. Hash Cond: (a.bid = b.id)
  16. -> Parallel Seq Scan on a (cost=0.00..66820.00 rows=312500 width=20)
  17. -> Hash (cost=20834.00..20834.00 rows=100 width=4)
  18. -> Seq Scan on b (cost=0.00..20834.00 rows=100 width=4)
  19. Filter: (path ~~ 'abcde%'::text)
  20. (8 rows)
  21. Time: 0.685 ms
  22. postgres=# select a.* from a left join b on (a.bid=b.id) where b.path like 'abcde%';
  23. id | bid | c1 | c2 | c3
  24. ---------+--------+---------+---------+---------
  25. 5004634 | 633740 | 5420943 | 2448245 | 5719976
  26. 9751456 | 633740 | 3797867 | 1934900 | 2511398
  27. 3851817 | 633740 | 8764393 | 3779499 | 2830950
  28. 4889541 | 633740 | 3892055 | 9470525 | 611262
  29. 6344344 | 633740 | 5869018 | 9352883 | 636112
  30. 2433286 | 633740 | 9797626 | 6737349 | 5669893
  31. 2423577 | 633740 | 846719 | 1720744 | 416608
  32. 6051251 | 633740 | 8691218 | 7184625 | 5940346
  33. 5372019 | 633740 | 5402891 | 3441462 | 8194368
  34. (9 rows)
  35. Time: 454.405 ms
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/630237
推荐阅读
相关标签
  

闽ICP备14008679号