Как да поддържаме ефективно таблица за преходно затваряне?

Имам DAG в моята релационна база данни (Firebird) с две таблици edge и node (модел на списък със съседство). Искам да ги запитам рекурсивно, но намерих рекурсивните заявки за много неефективни. Така че се опитах да внедря тригери, за да поддържам транзитивното затваряне след Dong et.al. хартия http://homepages.inf.ed.ac.uk/libkin/papers/tc-sql.pdf.

SELECTs вече са много бързи, но DELETEs са изключително бавни, защото почти цялата графика се копира за едно изтриване. Дори по-лошо, едновременните актуализации изглеждат невъзможни.

Има ли по-добър начин за прилагане на това?

Редактиране

Направих няколко експеримента и въведох референтен брояч към таблицата 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-Acyclic-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
@ChuckCottrill Искам да направя заявка за всички връзки рекурсивно. Например: кои са всички (непреки) родители или деца на възел 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
Наясно съм, че graph db би било идеалното решение; но за две графики с ‹ 100k ръбове всяка не добавям нова база данни. - person Christoph Walesch; 12.10.2013

Опитайте да създадете индекси за съответните клаузи къде (напр.: child, parent).

Не съм запознат с Firebird, но вижте как работи "firebird describe" върху него и проверете дали използва правилното използване на индекси, за да ускори изборите, които имате във вашите процедури.

Дори при създаване на индекси, които сте загубили в производителността за актуализиране/изтриване/вмъкване, във вашия случай това може да подобри резултата.

person VancleiP    schedule 14.10.2013
comment
Реалното изпълнение има индекси; Просто не копирах оператора CREATE INDEX в кода по-горе. - person Christoph Walesch; 14.10.2013