1

MySQL ストアド プロシージャにウォーシャルのアルゴリズムを実装しました。残念ながら、手続きが完了するまでに時間がかかります。私はストアド プロシージャの作成の初心者です。高速化するために何ができるか知っていますか?

簡単な説明: 隣接リストの推移閉包を計算しようとしています。どのノードが接続されているかを知りたい (1 つのエッジで直接、または複数のエッジで間接的に)。例えば:

Input:  (1, 2), (2, 3), (3, 4)
Output: (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)

次の図は、グラフを示しています。


ウィキメディア・コモンズからの画像: https://en.wikipedia.org/wiki/File:Transitive-closure.svg

SQL Fiddleまたはここでコードを表示できます。

# "Warshall's algorithm" to calculate the transitive closure
# (1) For k = 1 to n
# (2)   For i = 1 to n
# (3)     If d[i,k] = 1
# (4)       For j = 1 to n
# (5)         If d[k,j] = 1 : d[i,j] = 1
create procedure closure()
begin
    drop table if exists adjMatrix;
    drop table if exists idArray;
    create temporary table adjMatrix (idFrom int not null, idTo int not null,
                                      primary key (idFrom, idTo));
    create temporary table idArray (id int);
    insert into adjMatrix select parent_id, id
                          from article where parent_id is not null;
    insert into idArray select id from article;
    blockk: begin
        declare k, fink int;
        declare ck cursor for select id from idArray;
        declare continue handler for not found set fink = 1;
        open ck;
        loopk: loop
            fetch ck into k;
            if fink = 1 then
                leave loopk;
            end if;
            blocki: begin
                declare i, fini int;
                declare ci cursor for select id from idArray;
                declare continue handler for not found set fini = 1;
                -- select k from dual;
                open ci;
                loopi: loop
                    fetch ci into i;
                    if fini = 1 then
                        leave loopi;
                    end if;
                    blockj: begin
                        if exists (select * from adjMatrix where idFrom=i and idTo=k)
                        then
                            blockx: begin
                                declare j, finj int;
                                declare cj cursor for select id from idArray;
                                declare continue handler for not found set finj = 1;
                                open cj;
                                loopj: loop
                                    fetch cj into j;
                                    if finj = 1 then
                                        leave loopj;
                                    end if;
                                    if exists (select * from adjMatrix
                                               where idFrom=k and idTo=j) then
                                        insert into adjMatrix values (i, j);
                                    end if;
                                end loop loopj;
                                close cj;
                            end blockx;
                        end if;
                    end blockj;
                end loop loopi;
                close ci;
                -- select idFrom, idTo from adjMatrix order by idFrom, idTo;
            end blocki;
        end loop loopk;
        close ck;
    end blockk;
    insert into adjMatrix select id, id from article where parent_id is null;
    select idFrom, idTo from adjMatrix order by idFrom, idTo;
    drop temporary table adjMatrix;
    drop temporary table idArray;
end//

1466 レコードのテーブルで手順を実行すると、私のコンピューターでは 45 秒かかります。

mysql> call closure;
+--------+------+
| idFrom | idTo |
+--------+------+
|      1 |    1 |
|      1 |    2 |
|      1 |    3 |
|      1 |    4 |
|      1 |    5 |
|      2 |    3 |
|      2 |    4 |
|      2 |    5 |
|      3 |    4 |
|      3 |    5 |
|      4 |    5 |
~        ~      ~
|   1587 | 1587 |
|   1588 | 1588 |
|   1589 | 1589 |
+--------+------+
1472 rows in set (45.58 sec)
4

1 に答える 1

1

警告: 私は mysql に詳しくないので、問題を MSSQL に「翻訳」しました。そのため、元に戻すには多少の努力が必要です =)

物事が遅い理由は、SQL がこの種の操作に実際に向いていないからだと思います。分岐やループなどのすべてが「好き」ではありません。A LOT のように、あるテーブルから別のテーブルへ、できれば大きなヒープでデータを照合します。(RDBMS の R について考えてください)

したがって、ストアド プロシージャを高速化するには、このスタイルのコーディングにより適した別のプログラミング言語に切り替えることができます。または、問題をより適切な SQL に変換することもできます。もちろん、後者の方がずっと楽しいです!=)

この問題について少し考えてみると、次のようになりました。

CREATE TABLE #result (idFrom int not null, idTo int not null, primary key (idFrom, idTo));

INSERT INTO #result
SELECT parent_id, id
  FROM article 
 WHERE parent_id is not null;

 WHILE @@ROWCOUNT > 0
    BEGIN
        INSERT INTO #result 
        SELECT DISTINCT f.idFrom, t.idTo
          FROM #result f
          JOIN #result t
            ON t.idFrom = f.idTo
         WHERE NOT EXISTS ( SELECT *
                              FROM #result old
                             WHERE old.idFrom = f.idFrom
                               AND old.idTo = t.idTo )
    END

SELECT * FROM #result ORDER BY idFrom, idTo

繰り返しますが、これは TSQL (MSSQL で使用される SQL 方言) ですが、mysql (??) に変換するのはかなり簡単だと思います。

それが行うことは次のとおりです。

  • adjMatrix テーブルとほとんど同じ #result 一時テーブルを作成します
  • それらのソーステーブルから「直接リンク」をロードします
  • 1 つのレコード idTo を別のレコード idFrom に一致させることにより、すべての「二次組み合わせ」を挿入します。テーブルで上記の組み合わせがまだ見つからないことを確認し、上記のリストに一意の組み合わせのみがあることを確認します(個別)
  • 新しいレコード (および組み合わせ) が追加された場合は、「次の」レイヤーを追加できるかどうかを確認します。

新しいものが見つからなくなったら、結果を返します

例: 入力 (1, 2), (2, 3), (3, 4) が与えられた場合

最初に #result を (1, 2)、(2, 3)、(3, 4) で埋めてから、ループに入ります。

iteration1 は次のレコードに一致します。

  • (1,2)-(2,3) => (1,3)
  • (2,3)-(3,4) => (2,4)

これらを #result に追加すると、(1, 2)、(2, 3)、(3, 4)、(1, 3)、(2, 4) が見つかります。

iteration2 は次のレコードに一致します。

  • (1,2)-(2,3) => (1,3) ただし、WHERE NOT EXISTS() により削除されます
  • (1,2)-(2,4) => (1,4)
  • (2,3)-(3,4) => (2,4) ただし、WHERE NOT EXISTS() により削除されます
  • (1,3)-(3,4) => (1,4)

次に、上記のリストを DISTINCT し、(1,4) の 1 つのインスタンスのみが残り、追加されるため、(1, 2)、(2, 3)、(3, 4)、(1, 3)、(2、 4), (1,4)

iteration4 は次のレコードに一致します。

  • (1,2)-(2,3) => (1,3) ただし、WHERE NOT EXISTS() により削除されます
  • (1,2)-(2,4) => (1,4) ただし、WHERE NOT EXISTS() により削除されます
  • (2,3)-(3,4) => (2,4) ただし、WHERE NOT EXISTS() により削除されます
  • (1,3)-(3,4) => (1,4) ただし、WHERE NOT EXISTS() により削除されます

すべての新しいレコードが削除されると、行数がゼロになり、ループから飛び出します。

SqlFiddle入力でもアルゴリズムを試してみましたが、結果はほぼ瞬時に得られましたが、16ではなく15レコードの出力になりました。コードには (1, 1) レコードも含まれているようです。自分 ?!

とにかく、これが役立つことを願っています。しばらく SQL を使用すると、「値ごと」に作業する代わりに、データのブロックを操作する方法を自動的に学習します。これは、RDBMS を屈服させる可能性があるものです。通常、小さなデータセットでは問題なく動作しますが、データが追加されるとパフォーマンスが低下します。適切に作成されたセットベースの SQL は、100 レコードと 100000 レコードの処理の違いにほとんど気づきません。(いわば=)

于 2014-01-27T21:53:44.457 に答える