Как эффективно поддерживать транзитивную таблицу закрытия?

У меня есть DAG в моей реляционной базе данных (Firebird) с двумя таблицами edge и node (модель списка смежности). Я хочу запрашивать их рекурсивно, но нашел рекурсивные запросы очень неэффективными. Поэтому я попытался реализовать триггеры для поддержания транзитивного закрытия после Донга и др. документ http://homepages.inf.ed.ac.uk/libkin/papers/tc-sql.pdf.

SELECT теперь очень быстрые, а DELETE очень медленные, потому что почти весь граф копируется за одно удаление. Хуже того, одновременные обновления кажутся невозможными.

Есть ли лучший способ реализовать это?

Изменить

Я провел несколько экспериментов и ввел счетчик ссылок в таблицу TC. При этом удаления выполняются быстро. Я написал несколько простых тестов, но не уверен, что делаю правильно. Это то, что у меня есть до сих пор:

CREATE GENERATOR graph_tc_seq;

CREATE TABLE EDGE (
    parent DECIMAL(10, 0) NOT NULL,
    child DECIMAL(10, 0) NOT NULL,
    PRIMARY KEY (parent, child)
);

CREATE TABLE GRAPH_TC (
    parent DECIMAL(10, 0) NOT NULL,
    child DECIMAL(10, 0) NOT NULL,
    refcount DECIMAL(9, 0),
    PRIMARY KEY (parent, child)
);

CREATE TABLE GRAPH_TC_TEMP (
    session_id DECIMAL(9, 0),
    parent DECIMAL(10, 0),
    child DECIMAL(10, 0)
);

CREATE PROCEDURE GRAPH_TC_CREATE (p_parent DECIMAL(10, 0), c_child DECIMAL(10, 0))
AS 
    declare variable tp_parent DECIMAL(10,0);
    declare variable tc_child DECIMAL(10,0);
    declare variable session_id DECIMAL(9,0);
    declare variable refs DECIMAL(9,0);
begin
    session_id = gen_id(graph_tc_seq,1);
    insert into graph_tc_temp (parent, child, session_id, refcount) values (:p_parent, :p_parent, :session_id, 1);
    insert into graph_tc_temp (parent, child, session_id, refcount) values (:c_child, :c_child, :session_id, 1);
    insert into graph_tc_temp (parent, child, session_id, refcount) values (:p_parent, :c_child, :session_id, 1);
    insert into graph_tc_temp (parent, child, session_id, refcount) select distinct :p_parent, child, :session_id, refcount from graph_tc where parent = :c_child and not parent = child;
    insert into graph_tc_temp (child, parent, session_id, refcount) select distinct :c_child, parent, :session_id, refcount from graph_tc where child = :p_parent and not parent = child;
    insert into graph_tc_temp (parent, child, session_id, refcount) select distinct a.parent, b.child, :session_id, a.refcount*b.refcount from graph_tc a, graph_tc b where a.child = :p_parent and b.parent = :c_child and not a.parent = a.child and not b.parent = b.child;
    for select parent, child, refcount from graph_tc_temp e where session_id= :session_id and exists (select * from graph_tc t where t.parent = e.parent and t.child = e.child ) into :tp_parent, :tc_child, :refs do begin
        update graph_tc set refcount=refcount+ :refs where parent = :tp_parent and child = :tc_child;
    end
    insert into graph_tc (parent, child, refcount) select parent, child, refcount from graph_tc_temp e where session_id = :session_id and not exists (select * from graph_tc t where t.parent = e.parent and t.child = e.child);
    delete from graph_tc_temp where session_id = :session_id;
end ^

CREATE PROCEDURE GRAPH_TC_DELETE (p_parent DECIMAL(10, 0), c_child DECIMAL(10, 0))
AS 
    declare variable tp_parent DECIMAL(10,0);
    declare variable tc_child DECIMAL(10,0);
    declare variable refs DECIMAL(9,0);
begin
    delete from graph_tc where parent = :p_parent and child = :p_parent and refcount <= 1;
    update graph_tc set refcount = refcount - 1 where parent = :p_parent and child = :p_parent and refcount > 1;
    delete from graph_tc where parent = :c_child and child = :c_child and refcount <= 1;
    update graph_tc set refcount = refcount - 1 where parent = :c_child and child = :c_child and refcount > 1;
    delete from graph_tc where parent = :p_parent and child = :c_child and refcount <= 1;
    update graph_tc set refcount = refcount - 1 where parent = :p_parent and child = :c_child and refcount > 1;
    for select distinct :p_parent,  b.child, refcount from graph_tc b where b.parent = :c_child and not b.parent = b.child into :tp_parent, :tc_child, :refs do begin
        delete from graph_tc where parent = :tp_parent and child = :tc_child and refcount <= :refs;
        update graph_tc set refcount = refcount - :refs where parent = :tp_parent and child = :tc_child and refcount > :refs;
    end
    for select distinct :c_child,  b.parent, refcount from graph_tc b where b.child = :p_parent and not b.parent = b.child into :tc_child, :tp_parent, :refs do begin
        delete from graph_tc where child = :tc_child and parent = :tp_parent and refcount <= :refs;
        update graph_tc set refcount = refcount - :refs where child = :tc_child and parent = :tp_parent and refcount > :refs;
    end
    for select distinct a.parent, b.child, a.refcount*b.refcount from graph_tc a, graph_tc b where not a.parent = a.child and not b.parent = b.child and a.child = :p_parent and b.parent = :c_child into :tp_parent, :tc_child, :refs do begin
        delete from graph_tc where parent = :tp_parent and child = :tc_child and refcount <= :refs;
        update graph_tc set refcount = refcount - :refs where parent = :tp_parent and child = :tc_child and refcount > :refs;
    end
end ^

CREATE TRIGGER GRAPH_TC_AFTER_INSERT FOR EDGE AFTER INSERT as
begin
    execute procedure graph_tc_create(new.parent,new.child);
end ^

CREATE TRIGGER GRAPH_TC_AFTER_UPDATE FOR EDGE AFTER UPDATE as
begin
    if ((new.parent <> old.parent) or (new.child <> old.child)) then begin
    execute procedure graph_tc_delete(old.parent,old.child);
    execute procedure graph_tc_create(new.parent,new.child);
    end
end ^

CREATE TRIGGER GRAPH_TC_AFTER_DELETE FOR EDGE AFTER DELETE as
begin
    execute procedure graph_tc_delete(old.parent,old.child);
end ^

Это моя собственная идея, но я думаю, что другие уже реализовали TC. Они делают то же самое?

У меня есть несколько тестовых случаев, но я не уверен, могу ли я получить несоответствие с большими графиками.

Как насчет параллелизма, я думаю, что этот подход потерпит неудачу, когда две одновременные транзакции захотят обновить граф, верно?

Изменить

Я нашел несколько ошибок в своем коде и хочу поделиться с вами исправленной версией.

Я нашел отличную статью: http://www.codeproject.com/Articles/22824/A-Model-to-Represent-Directed-Acyclop-Graphs-DAG-o. Есть ли еще интересные статьи или научные труды, с разными подходами?


person Christoph Walesch    schedule 24.09.2013    source источник
comment
Можете ли вы включить (соответствующие части) определения DDL и триггеров?   -  person Mark Rotteveel    schedule 25.09.2013
comment
@MarkRotteveel смотрите мою правку   -  person Christoph Walesch    schedule 25.09.2013
comment
Рассмотрите возможность использования GTT (глобальной временной таблицы) вместо обычной таблицы для GRAPH_TC_TEMP   -  person Mark Rotteveel    schedule 25.09.2013
comment
Вы хотите запрашивать таблицы рекурсивно или запрашивать определенные графики и соединения?   -  person ChuckCottrill    schedule 08.10.2013
comment
@ChuckCottril Я хочу рекурсивно запрашивать все соединения. Например: каковы все (косвенные) родители или дети узла X? Какие узлы находятся между узлами X и Y?   -  person Christoph Walesch    schedule 08.10.2013


Ответы (3)


Я только что исправил медленную операцию удаления, расширив модель транзитивной рефлексивной таблицы закрытия, описанную здесь: http://www.dba-oracle.com/t_sql_patterns_incremental_eval.htm. Потребовалось немного больше работы, чтобы полностью поддерживать количество путей внутри него, но это окупилось, когда удаление сократилось с 6 секунд для каждой отдельной операции удаления до незначительного (теперь я могу удалить все отношения в графе, а затем добавить их все обратно). всего за 14 секунд на 4000 отношений).

person nclu    schedule 08.08.2014
comment
Кроме того, длины кратчайших путей можно поддерживать аналогично общему количеству путей tjhsst.edu/~rlatimer/acm/DatabaseSystems/ - person nclu; 26.08.2014

SQL не является подходящим инструментом для работы с графами. Используйте один из них:

http://en.wikipedia.org/wiki/Graph_database

Мне очень нравится ArangoDB, синтаксис которой близок к mongodb.

person JulienFr    schedule 11.10.2013
comment
Я знаю, что граф БД был бы идеальным решением; но для двух графов с ‹ 100 000 ребер каждый я не добавляю новую базу данных. - person Christoph Walesch; 12.10.2013

Попробуйте создать индексы для соответствующих предложений where (например, child, parent).

Я не знаком с Firebird, но посмотрите, как на нем работает "описание firebird", и проверьте, использует ли он правильное использование индексов для ускорения выбора, который у вас есть в ваших процедурах.

Даже создание индексов, которые вы потеряли в производительности для update/delete/insert, в вашем случае может улучшить результат.

person VancleiP    schedule 14.10.2013
comment
Реальная реализация имеет индексы; Я просто не скопировал оператор CREATE INDEX в приведенный выше код. - person Christoph Walesch; 14.10.2013