在 postgres 中,使用 split_part 函数代替 like 语句好吗

In postgres, is it good to use split_part function instead like statements

有人可以解释或建议我们可以使用 split_part 而不是 喜欢Postgres中的

在我的用例中,名称列将包含一些中间字符串,这对于特定类别是常见的。比如Vinod.Game1Vinod.Game2Vinod.Game3

现在我想获取 Vinod 玩的游戏数量及其详细信息。 我有两个选择:

select * from games where name like 'Vinod.Game%'

select * from games where split_part(name, '.Game', 1) = 'Vinod'

当我检查 200 行的数据时,我看到了 beloe 统计数据

对于喜欢的查询:

Planning time: 120.326 ms
 Execution time: 2.878 ms

对于split_part查询:

Planning time: 8.845 ms
 Execution time: 3.681 ms

能否请您帮助我理解计划时间在查询中的影响。哪个更好用(split_part vs like), 如果我们有千兆数据库?

                                              Table "public.games"
      Column      |         Type          | Collation | Nullable |        Default         | Storage  | Stats target | Description
------------------+-----------------------+-----------+----------+------------------------+----------+--------------+-------------
 id               | character varying(32) |           | not null |                        | extended |              |
 access           | character varying(50) |           |          |                        | extended |              |
 deleted          | character varying(1)  |           |          | 'N'::character varying | extended |              |
 timePlayed       | character varying(50) |           |          |                        | extended |              |
 description      | character varying     |           |          |                        | extended |              |
 name             | character varying(64) |           |          |                        | extended |              |

使用 LIKE 的第一个查询将扫描 table(不是索引扫描,因为丑陋的“SELECT *”...)。

带有 split_part 的第二个查询将构建一个内部数据集(split_part 函数),并对结果数据集进行扫描以查找符合条件的行。

在这两种情况下,根本没有索引可以加速您的查询,因为您的谓词不可搜索。

实际上您查询的数据违反了第一范式(atomci 数据)。当这样的 mistqke 完成时,您的数据库根本就不是关系型的,当您没有关系型数据库时,RDBMS 无法用性能来处理它,因为 RDBMS 专门设计用于操纵关系,而不是“cobol”类型的数据结构!

正确的解决方案是修复数据模型。

不要在单个列中存储分隔值。这会在漫长的运行.

中一次次伤害你

但是像这样的问题的答案通常是“我没有创建它,我必须忍受它”,您需要测试这两种方法。

要获得有意义的测试,您必须创建远不止 200 行的行。

我使用这种方法创建了一些假数据:

create table games
(
 id               character varying(32)  not null,
 access           character varying(50),
 deleted          character varying(1) default 'N'::character varying ,
 timePlayed       character varying(50),
 description      text ,
 name             character varying(64)                                   
);

insert into games(id, access, timeplayed, description, name)
select g.id::text, 
       'full',
       'all night long',
       'some description',
       case 
          when random() < 0.1 then 'Vinod.'
          when random() < 0.2 then 'Vivos.'
          when random() < 0.3 then 'Doniv.'
          when random() < 0.5 then 'Novid.'
          when random() < 0.6 then 'Somevid.'
          when random() < 0.7 then 'OtherVid.'
          when random() < 0.8 then 'Fonod.'
          else 'Barnod.'
       end || 'Game' || (random() * 999 + 1)::int
from generate_series(1,1e6) as g(id);

create index on games (name varchar_pattern_ops);
create index on games  ( (split_part(name, '.', 1)) );
vacuum analyze games;

以上生成了 100 万行,其中 10% 以 Vinod.

开头

请注意,我只在 . 上拆分,而不是在 .Game 上拆分 - 对我来说更有意义:选择第一个由点分隔的元素。

缓存 table 时,LIKE 查询的执行时间约为 70 毫秒,使用 split_part() 的查询的执行时间约为 25 毫秒(Windows 10 台笔记本电脑和 Postgres 13.2)。所以 split_part() 和基于表达式的索引似乎是赢家。

explain (analyze, buffers)
select * 
from games 
where name like 'Vinod.Game%';


QUERY PLAN                                                                                                                         
-----------------------------------------------------------------------------------------------------------------------------------
Index Scan using games_name_idx on games  (cost=0.42..12490.69 rows=99080 width=59) (actual time=0.018..67.705 rows=100189 loops=1)
  Index Cond: (((name)::text ~>=~ 'Vinod.Game'::text) AND ((name)::text ~<~ 'Vinod.Gamf'::text))                                   
  Filter: ((name)::text ~~ 'Vinod.Game%'::text)                                                                                    
  Buffers: shared hit=99863                                                                                                        
Planning Time: 0.669 ms                                                                                                            
Execution Time: 70.365 ms                                                                                                          
explain (analyze, buffers)
select * 
from games 
where split_part(name, '.', 1) = 'Vinod'

QUERY PLAN                                                                                                                               
-----------------------------------------------------------------------------------------------------------------------------------------
Index Scan using games_split_part_idx on games  (cost=0.42..11657.32 rows=99400 width=59) (actual time=0.025..20.793 rows=100189 loops=1)
  Index Cond: (split_part((name)::text, '.'::text, 1) = 'Vinod'::text)                                                                   
  Buffers: shared hit=11450                                                                                                              
Planning Time: 0.098 ms                                                                                                                  
Execution Time: 23.605 ms                                                                                                                

但再次声明:解决问题的正确方法是规范化数据模型。