来源:
互联网
作者:
若水
2008-03-19/10:19
[color=red:dfc2685962][b:dfc2685962] 奇怪的问题[/b:dfc2685962][/color:dfc2685962]
在你的数据库使用过程中遇到过以下的奇怪现象吗?
假设过程sp_select的功能是从某用户表中取得数据:
create procedure sp_select
as
select * from test
go
但是过程执行的结果与手工执行select * from test的结果就是不一样!
又譬如你使用alter table语句为表test增加了一列,但过程的输出总是不会显示出新加的列!
这些问题我在三年前的一次应用测试中首次遇到,其后又遇到两三次,当今天我的同事又来问我这个问题时,我觉得有必要总结一下了。
joey 回复于:2003-09-09 18:11:09[color=red:52017db352][b:52017db352] 问题的模拟[/b:52017db352][/color:52017db352]
如果你没有碰到过此类的问题,那么不妨先模拟一下这两个问题,然后再来寻求解决的办法。
为了方便下文的引用,我们称着两个问题为sp_rename问题和alter table问题。
lsp_rename问题的模拟
1)首先创建所有的过程与表
create table test ( a char(4))
insert into test select ‘OLD’
create proc sp_select as select * from test
试一下exec sp_select,输出为:
a
----
OLD
(1 row affected)
(return status = 0)
现在一切正常
2)将表test改名,并新建一个名为test的表(注意,新的表中没有数据)
sp_rename test, test_old
create table test ( a char(4))
insert into test select ‘NEW’
3)测试
这时问题出现了
select * from test的输出:
a
----
NEW
(1 row affected)
而sp_select的输出为:
a
----
OLD
(1 row affected)
(return status = 0)
sp_select仍然显示老表中的数据,而非表test中的内容
lalter table问题的模拟
1)首先创建所有的过程与表
create table test ( a char(4))
insert into test select ‘OLD’
create proc sp_select as select * from test
试一下exec sp_select,输出为:
a
----
OLD
(1 row affected)
(return status = 0)
2)给表test加一列
alter table test add b int null
update test set b = 1
3)测试,同样sp_select的输出与期望不符
sp_select的输出:
a
----
OLD
(1 row affected)
(return status = 0)
select * from test的输出
a b
---- -----------
OLD 1
(1 row affected)
两个问题都模拟完了,如果你没有明白这两个例子,那么不妨亲手试一下,直到理解了再往下看吧。
joey 回复于:2003-09-09 18:11:45[color=red:df598033da][b:df598033da] 问题的原因[/b:df598033da][/color:df598033da]
这两个问题的根本表现都在于手工执行select语句的输出都正确,而执行过程的输出却仍然指向原来的老表!
原因就在于手工的select语句是即时编译的,而过程是在创建时编译的。不仅如此,过程引用一个对象时,使用的是对象的Object ID,而非表名。
先用以上的原因来解释一下sp_rename问题吧:
表test创建时,假设ObjectID=999,创建sp_select过程后,编译的结果中包含了以下的操作 à 从ObjectID为999的表中取数据。
然后,sp_rename test, test_old改名后,请注意,sp_rename并不影响表的objectID,这时test_old表的ObjectID为999。
接下来创建了一个新的test表,它显然分配了新的ObjectID,假设为1000。
由于手工执行select * from test为即时编译,因此它会从ObjectID为1000的表中取得数据,而由于过程sp_select没有重新编译,则仍然会从ObjectID为999的表(老表)中去取数据,这样两者的结果就不一致了。
alter table的问题和ObjectID并没有关系,问题产生的原因同样和编译有关,在创建过程时,编译器将过程中的语句展开了,于是:
select * from test就变成了select a from test
尽管随后给表test添加了一列,但由于没有重新编译,因此执行过程时仍然只显示一列而非两列。
joey 回复于:2003-09-09 18:12:10[color=red:b1d7cdba0a][b:b1d7cdba0a] 问题的解决[/b:b1d7cdba0a][/color:b1d7cdba0a]
问题的原因知晓了,如何解决?从以上的分析中我们知道,导致问题的原因在于过程没有重新编译,如何能使得过程重新编译,我的答案是重建与原表相关的所有过程。这不是唯一的答案(后面会提到),但却是最好的和最广泛使用的解决方法。大家可以自己做一下验证,这里不再赘述。
在三年前,当我用重建的方法解决这个问题时,心中不免产生了小小的疑问,难道没有更好的解决方法?如果涉及原表的过程是一个,尚且好办,如果是很多过程,那可真是相当麻烦。首先我们必须找出所有与原表有关的过程,使用系统过程sp_depends就可以达到目的。有趣的是,即使用sp_rename将原表改名(例如由原来的test改为test_old),sp_depends test_old的输出和在改名前sp_depends test的结果完全一样,这进一步证实了前面的推测,即过程引用一个对象时,使用的是对象的Object ID,而非表名。找到所有相关的过程后,删除他们,然后重建。重建的话当然需要建立过程的脚本,如果你的版本管理做得好的话那没有问题,否则还是采用最保险的方法:先用defncopy倒出过程,这样可以保证过程是最新的。还有过程一定就能删除成功吗,曾经遇到过这样的情况,在生产系统中升级最新的过程,由于这个过程是最核心的业务,客户端几乎时刻在调用,因此,删除了好几次也不成功。想想如果更新了一个过程,而其它的过程没有更新是什么情况!这些步骤,漏掉一步,漏掉一个对象都可能导致问题!#p#分页标题#e#