关于对象重新编译的问题

  • 来源: 互联网 作者: 若水   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#

    评论 {{userinfo.comments}}

    {{money}}

    {{question.question}}

    A {{question.A}}
    B {{question.B}}
    C {{question.C}}
    D {{question.D}}
    提交

    驱动号 更多