Postgres没有使用索引来实现慢速功能

时间:2017-05-17 11:03:35

标签: sql postgresql

在我的数据库设计中,使用了很多功能。其中很多都很慢。因此,我认为在其中一些上创建索引以便使执行速度更快一点是一个明智的想法。 但是,我没有成功说服PostgreSQL(9.6)实际使用我的索引。

考虑这个表" user"

id integer | name jsonb
1          | {"last_names": ["Tester"], "first_names": ["Teddy","Eddy"]} 
2          | {"last_names": ["Miller"], "first_names": ["Lisa","Emma"]}   

通常,我需要将名称作为一个字符串,用一个查询来完成(称为" concat_name")

SELECT array_to_string(jsonb_arr2text_arr(name->'last_names'), ' ') || ', ' || array_to_string(jsonb_arr2text_arr(name->'first_names'), ' ');

我决定将该功能放入一个函数中,因为它用于多个表:

CREATE OR REPLACE FUNCTION public.concat_name(name jsonb)
  RETURNS text AS
$BODY$
  SELECT pg_sleep(50);
  SELECT array_to_string(jsonb_arr2text_arr(name->'last_names'), ' ') || ', ' || array_to_string(jsonb_arr2text_arr(name->'first_names'), ' ');
$BODY$
  LANGUAGE sql IMMUTABLE SECURITY DEFINER
  COST 100;

你看,为了真正测试它是否有效,我已经添加了一个"人工地"超时。 现在,我创建了一个索引,如:

CREATE INDEX user_concat_name_idx ON "user" (concat_name(name));

成功并占用预期时间(因为pg_sleep)。然后我运行一个查询:

SELECT concat_name(name) FROM "user";

但是,索引未被使用且查询速度很慢。相反,EXPLAIN告诉我刨床在"用户"上进行序列扫描。

我做了一些研究,很多人都说查询刨床认为如果表格很小或正在检索的数据集(几乎)是整个表格,它认为进行序列扫描比查找更有效索引。 但是,如果是功能,特别是慢功能,那对我来说没有任何意义。即使您查询只包含一行的表 - 如果查询包含每次执行需要50秒的函数,则使用函数索引可以大大减少执行时间。

因此,在我看来,查询规划器必须比较查找索引值所花费的时间与执行函数所花费的时间。表格或查询本​​身的大小(返回多少行)在这里根本不重要。而且,如果函数需要50秒才能执行,查找索引应该总是赢。

那么,我可以在这里做些什么来使查询平面器使用索引而不是每次重新执行该函数?

2 个答案:

答案 0 :(得分:2)

首先,如果您想在仅选择(id, concat_name(name))的查询中使用concat_name(name)上的索引,则没有任何意义。索引应为:

create index user_concat_name_idx on "user" (concat_name(name));

其次,索引将在需要时使用,例如当您添加order by concat_name(name)时:

explain analyse
select concat_name(name)
from "user"
order by 1;

                                                                  QUERY PLAN                                                                   
-----------------------------------------------------------------------------------------------------------------------------------------------
 Index Scan using user_concat_name_idx on "user"  (cost=0.42..29928.42 rows=100000 width=82) (actual time=0.157..1046.168 rows=100000 loops=1)
 Planning time: 0.753 ms
 Execution time: 1048.862 ms
(3 rows)

此外,您可以使您的功能更简单,更快捷:

create or replace function concat_name(name jsonb)
returns text language sql immutable as $$
    select concat_ws(', ',
        (select string_agg(value, ' ')
        from jsonb_array_elements_text(name->'last_names')),
        (select string_agg(value, ' ')
        from jsonb_array_elements_text(name->'first_names'))
    )
$$;
  

我可以在这里做什么来使查询刨床使用索引而不是每次重新执行该功能?

你应该声明一个更大的函数成本,例如:

create or replace function concat_name(name jsonb)
returns text language sql immutable as $$
-- ...
$$
cost 1000;

the documentation

  

execution_cost

     

一个正数,给出函数的估计执行成本,单位为cpu_operator_cost。如果函数返回一个集合,则这是每个返回行的成本。如果未指定成本,则假定C语言和内部函数为1个单位,而所有其他语言的函数为100个单位。较大的值会导致规划人员试图避免不必要地评估函数。

答案 1 :(得分:0)

通常,当您在varchar / text列上使用任何函数时,例如子句中的upper,lower,postgres不会考虑您的常规索引,而是对所有行进行全面扫描。您需要为此目的而建立的索引。例如

create index ix_tblname_col_upper on tblname (UPPER(col) varchar_pattern_ops);

类似地,您也可以在文本列上使用text_pattern_ops。