分析delphi的bug1:无法为更新定位行。

使用oracle的sql_tracle分析delphi的bug:“无法为更新定位行。一些值可能已在最后一次读取后已更改。”。

作者:heinigg
日期:2006.04.02
邮箱:heinigg@163.com

在delphi中,使用TADOQuery连接oracle的表的时候,这个错误比较常见。
很久很久以前就发现了这个问题,但没有找到答案。
有一天想起来用vb试试,发现vb没有问题。嘿嘿,vb真好啊。
又有一天帮别人下载了toad,无意中发现了toad的sql monitor,测试了一下vb和delphi发到oracle的sql,居然发现了问题所在。嘿嘿,帮助别人又帮助了自己。
我用sql_trace来实现同样的分析,猜想toad用的也应该是sql_trace。

OK,下面来复现这个错误。

测试的环境如下:
    window2000 server sp4
    MDAC_TYP 2.8
    oracle 9.2.0.1
    delphi7.0 + 7.1update
    vb6

step 1:首先,在oracle上建表
    C:/>sqlplus /nolog

    SQL> connect scott/tiger@heinigg
    已连接。
    SQL> create table test
      2  ( c1  integer,
      3    c2  varchar2(10)
      4  );

    表已创建。

    SQL> alter table test add primary key (c1);

    表已更改。

step 2:在delphi中建立程序
    TForm1 = class(TForm)
      DBNavigator1: TDBNavigator;
      DBGrid1: TDBGrid;
      DataSource1: TDataSource;
      ADOConnection1: TADOConnection;
      ADOQuery1: TADOQuery;
      Memo1: TMemo;
      Button1: TButton;
      procedure Button1Click(Sender: TObject);
    private
      { Private declarations }
    public
      { Public declarations }
    end;

    procedure TForm1.Button1Click(Sender: TObject);
    begin
      ADOQuery1.Connection := ADOConnection1;
      DataSource1.DataSet := ADOQuery1;
      DBNavigator1.DataSource := DataSource1;
      DBGrid1.DataSource := DataSource1;

      with ADOConnection1 do
      begin
        Close;
        ConnectionString :=
          'Provider=MSDAORA.1;' +
          'Password=tiger;User ID=scott;Data Source=heinigg';
        LoginPrompt := False;
        Open;
      end;

      with ADOQuery1 do
      begin
        Close;
        SQL.Clear;
        SQL.Add('select * from test');
        Open;
      end;
    end;

step 3:复现bug
    运行程序,打开表后,增加一条记录,数值为(1, 'heinigg'),保存。
    将c2的值清空,保存。
    再写值,出错。

step 4:跟踪oracle的sql
    运行程序,打开表后,在oracle中开始跟踪
    SQL> connect sys/heinigg as sysdba
    已连接。
    SQL> select sid, serial#, username
      2  from v$session
      3  where username='SCOTT';

           SID    SERIAL# USERNAME
    ---------- ---------- ------------------------------
            12        291 SCOTT

    SQL> execute dbms_system.set_ev(12, 291, 10046, 4, '');

    PL/SQL 过程已成功完成。

    在dbgrid1中,增加一条记录,数值为(1, 'heinigg'),保存。
    将c2的值清空,保存。
    再写值,出错。

    停止跟踪
    SQL> execute dbms_system.set_ev(12, 291, 10046, 0, '');

    PL/SQL 过程已成功完成。


    用vb建立一个工程,使用Adodc,DataGrid做一个同样的操作。

    这样,得到了delphi和vb的跟踪文件:oracle/admin/db92/udump/*.trc

    前2句的解析是一样的。
    INSERT INTO "TEST" ("C1","C2") VALUES (1,:V00002)
    bind 0: value="heinigg"

    UPDATE "TEST" SET "C2"=:V00001 WHERE "C2"=:V00002 AND "ROWID"=:V00003
    bind 0:
    bind 1: value="heinigg"
    bind 2: value="AAAH7DAABAAAMmaAAB"

    第3句
    delphi的sql语句是
    UPDATE "TEST" SET "C2"=:V00001 WHERE "C2"=:V00002 AND "ROWID"=:V00003
    bind 0: value="heinigg"
    bind 1:
    bind 2: value="AAAH7DAABAAAMmaAAB"

    vb的sql语句是
    UPDATE "TEST" SET "C2"=:V00001 WHERE "C2" IS NULL AND "ROWID"=:V00002
    bind 0: value="heinigg"
    bind 1: value="AAAH7DAABAAAMmaAAA"

    问题在哪呢?
    oracle中对于varchar和varchar2类型的字段只有2种,not null和null。
    update test set c2=''和update test set c2=null的结果是一样的,c2最后的值都是null。
    但是select * from test where c2=''和select * from test where c2 is null却不一样。

    个人认为,既然oracle能把set c2=''转化为set c2=null,为什么不把where c2=''转化为where c2 is null呢?嘿嘿,是不是要求高了点啊?

    ms sql server中对于varchar类型的字段有3种,not null、null和''。

step 5:解决办法
    很久以前,我用了一个很笨的方法。就是把数据提交后,再重新打开数据集。
    后来发现其实有个更好的办法。

    打开adodb.pas,TCustomADODataSet.SetFieldData过程

    把
    ftString, ftFixedChar, ftGuid:
      Data := WideString(PChar(Buffer));
    ftWideString:
      Data := WideString(Buffer^);

    修改为
    ftString, ftFixedChar, ftGuid:
    begin
      Data := WideString(PChar(Buffer));
      if Data = '' then Data := Null;
    end;
    ftWideString:
    begin
      Data := WideString(Buffer^);
      if Data = '' then Data := Null;
    end;

    嘿嘿,再测试一下原来程序,没有问题了。不过,我还没有生产中使用过,不知道会不会带来新的问题。
    对于有''类型的,原来的代码还是正确的,不能做如上修改。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值