【问题标题】:Postgresql Partition - Funtion calling in Select Query - is slowPostgresql 分区 - 选择查询中的函数调用 - 很慢
【发布时间】:2021-12-15 23:45:26
【问题描述】:

我们的系统是基于 SAAS 的系统,我们使用 ClientID 作为数据提取的掩码。 数据库负载基于公司的规模。所以我们根据 ClientID 对 DB 进行了分区

示例:分区前

客户端表

clientid clientname clientaddress
1 ABC ...
2 EMN ...
3 XYZ ...

可雇佣

clientid employeeid employeename
1 123 AAA
1 124 BBB
2 125 CCC
2 126 DDD
3 127 EEEE

工作表

clientid jobid jobname
1 234 YTR
1 235 DER
2 236 SWE
3 237 VFT
3 238 GHJ

示例:分区后

客户端

clientid clientname clientaddress
1 ABC ...
2 EMN ...
3 XYZ ...

可雇佣

employeetable_1

clientid employeeid employeename
1 123 AAA
1 124 BBB

employeetable_2

clientid employeeid employeename
2 125 CCC
2 126 DDD

employeetable_3

clientid employeeid employeename
3 127 EEE

工作表

jobtable_1

clientid jobid jobname
1 234 YTR
1 235 DER

jobtable_2

clientid jobid jobname
2 236 SWE

jobtable_3

clientid jobid jobname
3 237 VFT
3 238 GHJ

当我们编写选择查询时:

Select employeeid,employeename from employeetable where clientid=2;

此查询在分区后运行得更快。我们面临的问题是我们有一些用户定义的函数来操作一些数据。

CREATE OR REPLACE FUNCTION GET_JOB_COUNT(NUMERIC, NUMERIC) RETURNS NUMERIC AS $BODY$
   DECLARE
    p_client_id             ALIAS FOR $1;
    p_employee_id       ALIAS FOR $2;
    v_is_count      NUMERIC := 0;
   BEGIN  

    SELECT COUNT(JOB_ID) INTO v_is_count FROM JOBTABLE where CLIENTID=p_client_id AND CREATEDBY=p_employee_id;
        RETURN v_is_count;
   
   END; $BODY$
LANGUAGE plpgsql;

Select employeeid,employeename,GET_JOB_COUNT(2,employeeid) from employeetable where clientid=2;

分区后这个查询很慢。这是否意味着 GET_JOB_COUNT 函数是跨 Partition 运行的?

是不是这个问题,那么我们不能在分区后的Select查询中使用这样的函数?

【问题讨论】:

  • 该函数将为每一行调用一次(对于使用clientid = 2选择的所有行
  • 您是如何对表进行分区的?您使用的是哪个 Postgres 版本?
  • 我们在 x86_64-pc-linux-gnu 上使用 PostgreSQL 13.4
  • 我们根据“ClientID”进行分区,请参阅问题中的“分区后部分”
  • 列的类型有哪些?如果您将整数与数字进行比较,则可能会抑制分区修剪。

标签: postgresql spring-boot hibernate database-partitioning


【解决方案1】:

该函数将为雇员表中的每一行调用一次(通过 WHERE 子句选择)。我怀疑您可以使用这种方法以任何显着的方式提高性能。

最好一次对所有行进行聚合(=count),而不是单独对每一行进行聚合:

select e.employeeid, employeename, t.cnt 
from employeetable e 
  left join (
    select clientid, createdby, count(job_id) as cnt 
    from jobtable 
    group by client_id, created_by
  ) j on j.clientid = e.clientid and j.createdby = e.employeeid 
where e.clientid = 2;

另一个尝试的选择是使用横向连接来尽早从作业表中删除行 - 我不确定优化器是否足够智能以应对上述查询。所以你可以试试这个作为替代方案:

select e.employeeid, employeename, j.cnt 
from employeetable e 
  left join lateral (
    select count(jt.job_id) as cnt 
    from jobtable jt 
    where jt.clientid = e.clientid
      and jtcreatedby = e.employeeid
  ) j on true 
where e.clientid = 2;

如果您确实想坚持使用该函数,也许将其设为 SQL 函数有助于优化器。它至少消除了调用 PL/pgSQL 代码的开销:

CREATE OR REPLACE FUNCTION get_job_count(p_client_id numeric, p_employee_id numeric) 
  returns bigint 
as 
$body$
  SELECT COUNT(JOB_ID) 
  FROM JOBTABLE 
  where CLIENTID = p_client_id 
    AND CREATEDBY = p_employee_id;
$BODY$
LANGUAGE sql
stable
parallel safe;

但我怀疑你会因此看到实质性的改善。

另一方面:使用numeric 作为“ID”列似乎是一个相当奇怪的选择。你为什么不使用intbigint 呢?

【讨论】:

  • 这里给出的 PLSQL 计数函数只是一个示例,我们在函数内部完成了复杂的操作,我们无法通过左外连接/子查询/SQL 函数来解决。无论如何,从您的回答中,我们推断函数调用是 select 查询将为所选行的每一行而不是在每个分区上运行,谢谢。
  • @RSK:如果要按分区运行函数,则需要将其设置为返回函数,将分区键传递给它,然后您可以加入函数的输出
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-06
  • 2019-07-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多