我的关系数据库(Firebird)中有一个DAG,它有两个表edge
和node
(邻接列表模型)。我想以递归方式查询它们,但发现递归查询非常低效。因此,我试图在Dong et.al之后实现触发器以维持传递闭包。论文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-Acyclic-Graphs-DAG-o。是否有更多有趣的文章或科学论文,采用不同的方法?
答案 0 :(得分:3)
答案 1 :(得分:1)
我只是通过扩展到这里描述的传递反身闭包表模型来修复慢速删除操作: http://www.dba-oracle.com/t_sql_patterns_incremental_eval.htm。完全维护其中的路径计数需要花费更多的工作,但是当删除从每个单独的删除操作6秒到可忽略的时候它会得到很大的回报(我现在可以删除图中的每个关系,然后将它们全部添加回来) 4,000次关系总计14秒。)
答案 2 :(得分:0)
尝试为相关的 where 子句创建索引(例如:child
,parent
)。
我对Firebird并不熟悉,但看看“火鸟描述”是如何工作的,并检查它是否正在使用正确的索引来加速您在程序中的选择。
甚至创建在upddate / delete / insert的性能中丢失的索引,在您的情况下,它可以改善结果。