【问题标题】:Why postgres function so slow but single query is fast?为什么 postgres 功能这么慢,但单次查询却快?
【发布时间】:2015-03-17 03:02:26
【问题描述】:

我有让员工处于“创建”状态的功能。

    CREATE OR REPLACE FUNCTION get_probation_contract(AccountOrEmpcode TEXT, FromDate DATE,
                                                  ToDate           DATE)
  RETURNS TABLE("EmpId" INTEGER, "EmpCode" CHARACTER VARYING,
  "DomainAccount" CHARACTER VARYING, "JoinDate" DATE,
  "ContractTypeCode" CHARACTER VARYING, "ContractTypeName" CHARACTER VARYING,
  "ContractFrom" DATE, "ContractTo" DATE, "ContractType" CHARACTER VARYING,
  "Signal" CHARACTER VARYING) AS $$
BEGIN
  RETURN QUERY
  EXECUTE 'SELECT
    he.id                                                                       "EmpId",
    rr.code                                                                     "EmpCode",
    he.login                                                                    "DomainAccount",
    he.join_date                                                                "JoinDate",
    contract_type.code                                                          "ContractTypeCode",
    contract_type.name                                                          "ContractTypeName",
    contract.date_start                                                         "ContractFrom",
    contract.date_end                                                           "ContractTo",
    CASE WHEN contract_group.code = ''1'' THEN ''Probation''
    WHEN contract_group.code IN (''3'', ''4'', ''5'') THEN ''Official''
    WHEN contract_group.code = ''2'' THEN ''Collaborator'' END :: CHARACTER VARYING "ContractType",
    ''CREATE'' :: CHARACTER VARYING                                               "Signal"
  FROM
    hr_employee he
    INNER JOIN resource_resource rr
      ON rr.id = he.resource_id
    INNER JOIN hr_contract contract
      ON contract.employee_id = he.id AND contract.date_start = (
      SELECT max(date_start) "date_start"
      FROM hr_contract cc
      WHERE cc.employee_id = contract.employee_id
    )
    INNER JOIN hr_contract_type contract_type
      ON contract_type.id = contract.type_id
    INNER JOIN hr_contract_type_group contract_group
      ON contract_group.id = contract_type.contract_type_group_id
  WHERE
    contract_group.code = ''1''


    AND
    ($1 IS NULL OR $1 = '''' OR rr.code = $1 OR
     he.login = $1)
    AND (
      (he.join_date BETWEEN $2 AND $3)
      OR (he.join_date IS NOT NULL AND (contract.date_start BETWEEN $2 AND $3))
      OR (he.create_date BETWEEN $2 AND $3 AND he.create_date > he.join_date)
    )
    AND rr.active = TRUE
'using AccountOrEmpcode, FromDate, ToDate ;
END;
$$ LANGUAGE plpgsql;

执行耗时 37 秒

SELECT *
 FROM get_probation_contract('', '2014-01-01', '2014-06-01');

当我使用单个查询时

    SELECT
      he.id                                                                       "EmpId",
      rr.code                                                                     "EmpCode",
      he.login                                                                    "DomainAccount",
      he.join_date                                                                "JoinDate",
      contract_type.code                                                          "ContractTypeCode",
      contract_type.name                                                          "ContractTypeName",
      contract.date_start                                                         "ContractFrom",
      contract.date_end                                                           "ContractTo",
      CASE WHEN contract_group.code = '1' THEN 'Probation'
      WHEN contract_group.code IN ('3', '4', '5') THEN 'Official'
      WHEN contract_group.code = '2' THEN 'Collaborator' END :: CHARACTER VARYING "ContractType",
      'CREATE' :: CHARACTER VARYING                                               "Signal"
    FROM
      hr_employee he
      INNER JOIN resource_resource rr
        ON rr.id = he.resource_id
      INNER JOIN hr_contract contract
        ON contract.employee_id = he.id AND contract.date_start = (
        SELECT max(date_start) "date_start"
        FROM hr_contract
        WHERE employee_id = he.id
      )
      INNER JOIN hr_contract_type contract_type
        ON contract_type.id = contract.type_id
      INNER JOIN hr_contract_type_group contract_group
        ON contract_group.id = contract_type.contract_type_group_id
    WHERE
      contract_group.code = '1'
AND (
      (he.join_date BETWEEN '2014-01-01' AND '2014-06-01')
      OR (he.join_date IS NOT NULL AND (contract.date_start BETWEEN '2014-01-01' AND '2014-01-06'))
      OR (he.create_date BETWEEN '2014-01-01' AND '2014-01-06' AND he.create_date > he.join_date)
    )
    AND rr.active = TRUE

完成需要 5 秒

如何优化上面的功能。 以及为什么函数比单个查询慢这么多,即使我在函数中使用执行'select ...'。

在每个表的字段 id 中建立索引。

【问题讨论】:

  • 您似乎错过了测试或查询粘贴中的一部分:在 contract_group.code = '1' 之后,您在函数中有 AND ETC。还提供计划输出、索引以查看是否有提示。
  • 感谢您的评论。我添加了查询和索引信息。
  • 抱歉,您的测试查询中仍然缺少一部分:AND ($1 IS NULL OR $1 = '''' OR rr.code = $1 OR he.login = $1) 加上您的提示“您的测试中似乎缺少使用 AccountOrEmpcode, FromDate, ToDate "。
  • 我没有在问题中看到您的 Postgres 版本?并且只有id 列被索引?
  • 以下答案是否有帮助,@giaosudau?

标签: postgresql plpgsql postgresql-performance


【解决方案1】:

可能的原因是对预处理语句(嵌入式 SQL)的盲目优化。它在新的 PostgreSQL 版本中稍微好一点,尽管它也可能是那里的问题。 PL/pgSQL 中嵌入式 SQL 中的执行计划被重用于更多调用 - 并且针对更频繁的值(而不是真正使用的值)进行了优化。有时,这种差异会造成非常大的减速。

然后您可以使用动态 SQL - EXECUTE 语句。动态 SQL 只使用一次执行的计划,它使用实际参数。它应该解决这个问题。

具有重复使用的预准备计划的嵌入式 SQL 示例。

CREATE OR REPLACE FUNCTION fx1(_surname text)
RETURNS int AS $$
BEGIN
  RETURN (SELECT count(*) FROM people WHERE surname = _surname)
END;

动态 SQL 示例:

CREATE OR REPLACE FUNCTION fx2(_surname text)
RETURNS int AS $$
DECLARE result int;
BEGIN
  EXECUTE 'SELECT count(*) FROM people WHERE surname = $1' INTO result
      USING _surname;
  RETURN result;
END;
$$ LANGUAGE plpgsql;

如果你的数据集包含一些可怕的姓氏,第二个函数会更快——那么常见的计划将是seq scan,但很多时候你会问一些其他的姓氏,你会想要使用index scan。动态查询参数化(如($1 IS NULL OR $1 = '''' OR rr.code = $1 OR)具有相同的效果。

【讨论】:

  • 这似乎没有加起来。 OP 已经在使用EXECUTE
【解决方案2】:

您的查询不一样

第一个有

WHERE cc.employee_id = contract.employee_id

第二个有:

WHERE employee_id = he.id

还有:

($1 IS NULL OR $1 = '''' OR rr.code = $1 OR
 he.login = $1)

请使用相同个查询和相同的值再次测试。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-11
    • 1970-01-01
    • 2020-09-06
    • 1970-01-01
    • 2017-01-21
    • 2012-06-13
    • 2018-08-19
    相关资源
    最近更新 更多