使用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;
嘿嘿,再测试一下原来程序,没有问题了。不过,我还没有生产中使用过,不知道会不会带来新的问题。
对于有''类型的,原来的代码还是正确的,不能做如上修改。
作者: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;
嘿嘿,再测试一下原来程序,没有问题了。不过,我还没有生产中使用过,不知道会不会带来新的问题。
对于有''类型的,原来的代码还是正确的,不能做如上修改。