我在MySQL存储过程中实现了Warshall的算法。不幸的是,该过程需要很长时间才能完成。我是编写存储过程的初学者,你知道我能做什么,让它更快吗?
简要说明:我正在尝试计算邻接列表的传递闭包。我想知道,哪些节点是连接的(直接在一条边上,或间接在多条边上)。例如:
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)
答案 0 :(得分: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(??)应该非常简单。
它的作用是:
一旦找不到新内容,请返回结果
例如:给定输入(1,2),(2,3),(3,4)
首先用(1,2),(2,3),(3,4)填充#result 然后进入循环:
iteration1将匹配以下记录:
并将其添加到#result,我们因此找到(1,2),(2,3),(3,4),(1,3),(2,4)
iteration2将匹配以下记录:
然后DISTINCT表示列表,只剩下(1,4)的一个实例并将被添加 找到(1,2),(2,3),(3,4),(1,3),(2,4),(1,4)
iteration4将匹配以下记录:
随着所有新记录的消除,我们最终得到一个零行数,因此跳出循环。
我已经尝试了使用SqlFiddle输入的算法,结果几乎是瞬间但我最终输出的是15条记录而不是16条。看来你的代码还包括一条(1,1)记录有点让我感到惊讶?!
无论如何,希望这会有所帮助。使用SQL一段时间之后,您将自动学会对数据块进行操作,而不是使用“按值计算”,这可能会使任何RDBMS陷入困境。它通常适用于一小组数据,但是一旦输入更多数据,性能就会向南移动。编写良好的基于集合的SQL几乎不会注意到处理100或100000条记录之间的区别。 (可以这么说=)