前段时间在客户那封闭开发,要在数据库中做一个自动“裁货”的功能。
于是就写了一个存储过程,由于业务比较复杂,一张SO首先要通过备货合同分配库存,然后通过实际可用库存进行分配,再用在途的PO数量分配,最后用非在途的PO进行分配,所以每条SO要单独处理,于是就用到了游标。
哗啦哗啦,一千多行代码写完了,于是就在数据库中测试,问题一个一个的排除了,然后就把该功能放到程序中,让客户测试,几天过去了,偶尔会有一些小问题,总体效果还行,慢慢完善后运行的时间也从之前的6分多钟变到了后面的2分多钟,最好的情况是不到一分钟就搞定了,相对于老大之前提的10分钟之内这一要求来说,还是让人比较满意的。
客户也比较接受该功能,于是就开始正式试用该功能了,一个多星期过去了,突然有一天客户说,这个“裁货”功能怎么要运行这么久,当时第一感觉是一惊,不会吧!用了那么长时间了,应该不会有这情况吧!然后自己试着运行一下,1分钟过去了,10分钟过去了,30分钟过去了,实在忍受不了了,这时的感觉是有死循环了。于是创造条件进行排查。
哎!一千多行的代码啊!!!怎么查?搞来搞去,东搞西搞,这搞那搞,大半天时间过去了,也快到忍耐的极限了,finally,发现是在处理在途的PO的时候会出现这样的问题。然后尝试变换不同的条件运行,最后将游标中的一条语句做为重点的怀疑对象。
于是想确认一下到底这条语句导致的,就在一个测试库中写了如下的代码:
IF OBJECT_ID('TB') >0
DROP TABLE TB
CREATE TABLE TB
(
A INT,
B VARCHAR(10)
)
GO
INSERT INTO TB(A,B)
SELECT 1,'A'
UNION ALL
SELECT 2,'B'
UNION ALL
SELECT 3,'C'
SELECT * FROM TB
--结果:A B1 A
2 B
3 C
DECLARE @A INT, @B VARCHAR(10) DECLARE CUR CURSOR FOR SELECT A,B FROM TB OPEN CUR FETCH NEXT FROM CUR INTO @A,@B WHILE @@FETCH_STATUS = 0 BEGIN PRINT @B--一堆处理游标所指的记录的语句--将游标所指的记录进行拆分,复制出另外一条记录,并插入到游标所处理的表。INSERT INTO TB (A,B) SELECT @A,@B FETCH NEXT FROM CUR INTO @A,@B END CLOSE CUR DEALLOCATE CUR
--运行这段代码发现一直在运行,消息框中也一直在输出数据,于是赶紧终止运行,然后再查。SELECT * FROM TB 结果就恐怖了,TB表中多了几万行记录。。。
也就确认了是这个问题导致的。
现在回过头来看,可能这个问题很简单,但是当时由于游标中的代码太长了,很难注意到这种情况,也就导致了上面提到的情况的发生了。
后来通过引入一个物理意义上的临时表来解决了这个问题。
DECLARE @A INT, @B VARCHAR(10)
DECLARE CUR CURSOR FOR
SELECT A,B FROM TB
OPEN CUR
FETCH NEXT FROM CUR INTO @A,@B
WHILE @@FETCH_STATUS = 0
BEGIN
PRINT @B
--一堆处理游标所指的记录的语句
--将游标所指的记录进行拆分,复制出另外一条记录,并插入到游标所处理的表。
--INSERT INTO TB (A,B)
--SELECT @A,@B INSERT INTO TBTMP(A,B) --引入的物理意义上的临时表 SELECT @A,@B
FETCH NEXT FROM CUR INTO @A,@B
END
CLOSE CUR
DEALLOCATE CUR
INSERT INTO TB(A,B)SELECT A,B FROM TBTMP
就OK了。
谨以此纪念我那无数死去的脑细胞!!!

本文介绍了一次解决数据库中复杂存储过程中出现的死循环问题的经历,通过引入临时表的方法成功解决了由游标引发的问题。

1万+

被折叠的 条评论
为什么被折叠?



