优化plpgsql函数

时间:2012-07-24 01:56:58

标签: sql performance postgresql insert plpgsql

PostgreSQL版本是9.0。

我必须优化plpgsql函数。这个想法只是运行所有文档并测试表webdte.doc_tip_cifra中的相关行902,903,905,907是否已经存在。如果它们尚不存在,则插入空行以满足之后的验证。即使我只使用4个条件中的一个并且使用它必须运行的行数的一半,它此刻的速度非常慢。任何人都有提高性能的想法?

CREATE OR REPLACE FUNCTION webdte.addtagobligatoriosventa(idlibro bigint)
  RETURNS character AS
$BODY$
DECLARE                 
    id_documento bigint;
    validador integer;
    validador1 integer;
    validador2 integer;
    validador3 integer;
    validador4 integer;

    tipo_cifra integer;
    --counts integer[];
BEGIN
    SELECT INTO validador1, validador2, validador3, validador4
             max(CASE id_tipo_cifra WHEN 901 THEN 1 ELSE 0 END)
            ,max(CASE id_tipo_cifra WHEN 902 THEN 1 ELSE 0 END)
            ,max(CASE id_tipo_cifra WHEN 905 THEN 1 ELSE 0 END)
            ,max(CASE id_tipo_cifra WHEN 907 THEN 1 ELSE 0 END)
    FROM   webdte.doc_tip_cifra
    WHERE  id_doc = id_documento;

    if (validador1 = 0) then
        insert into webdte.doc_tip_cifra (id_doc, id_tipo_cifra, tasa_imp, val_imp)
        values (id_documento, 901, 0, 0);

    end if;
        if (validador2 = 0) then
        insert into webdte.doc_tip_cifra (id_doc, id_tipo_cifra, tasa_imp, val_imp)
        values (id_documento, 902, 0, 0);

    end if;
        if (validador3 = 0) then
        insert into webdte.doc_tip_cifra (id_doc, id_tipo_cifra, tasa_imp, val_imp)
        values (id_documento, 905, 0, 0);

    end if;
        if (validador4 = 0) then
        insert into webdte.doc_tip_cifra (id_doc, id_tipo_cifra, tasa_imp, val_imp)
        values (id_documento, 907, 0, 0);

    end if;

END;
$BODY$
  LANGUAGE plpgsql VOLATILE;

也许最好决定插入触发器,在每个文档插入的doc_tip_cifra上插入4个空行,以避免在所有文档上进行这种愚蠢的昂贵循环,并为每个文档测试4次? 你觉得怎么样?

1 个答案:

答案 0 :(得分:2)

原来,你实际上并不需要计算。您的preceding question传达了这种印象。但是,在我的解决方案中用sum替换max并不会让您走得太远。

它有效,是的,但是它非常低效。找到匹配的行后,您不必遍历表的其余部分。这就是EXISTS semi-joins的用途。我提出了这种完全不同的方法:

INSERT INTO webdte.doc_tip_cifra (id_doc, id_tipo_cifra, tasa_imp, val_imp)
SELECT id_documento, 901, 0, 0
WHERE  NOT EXISTS (
    SELECT 1
    FROM   webdte.doc_tip_cifra
    WHERE  id_doc = id_documento
    AND    id_tipo_cifra = 901
    );

INSERT INTO webdte.doc_tip_cifra (id_doc, id_tipo_cifra, tasa_imp, val_imp)
SELECT id_documento, 902, 0, 0
WHERE  NOT EXISTS (
    SELECT 1
    FROM   webdte.doc_tip_cifra
    WHERE  id_doc = id_documento
    AND    id_tipo_cifra = 902
    );

INSERT INTO webdte.doc_tip_cifra (id_doc, id_tipo_cifra, tasa_imp, val_imp)
SELECT id_documento, 905, 0, 0
WHERE  NOT EXISTS (
    SELECT 1
    FROM   webdte.doc_tip_cifra
    WHERE  id_doc = id_documento
    AND    id_tipo_cifra = 905
    );

INSERT INTO webdte.doc_tip_cifra (id_doc, id_tipo_cifra, tasa_imp, val_imp)
SELECT id_documento, 907, 0, 0
WHERE  NOT EXISTS (
    SELECT 1
    FROM   webdte.doc_tip_cifra
    WHERE  id_doc = id_documento
    AND    id_tipo_cifra = 907
    );

您可以将它包装在plpgsql或sql函数中,也可以将它作为纯SQL运行。

除了您之前的问题,这很可能会使用合适的索引。最佳值为multi-column index,如:

CREATE INDEX doc_tip_cifra_special_idx
ON webdte.doc_tip_cifra (id_doc, id_tipo_cifra);

应该让你的问题快速解决。

此外,这种算法存在并发性的固有问题。检查行是否已存在并插入行之间的时间窗口应尽可能小。在这方面,将所有内容放在一个查询中是最佳的。

尽管如此,它并非完美。如果您的数据库看到很多并发性,您可能会对此excellent blog post by @depesz感兴趣或阅读更多under this related question


是的,用触发器解决这个问题听起来是个好主意。我会这样做的。