Delphi 中的 BDE 与 ADO
Posted
技术标签:
【中文标题】Delphi 中的 BDE 与 ADO【英文标题】:BDE vs ADO in Delphi 【发布时间】:2010-09-27 00:27:42 【问题描述】:请注意下面的编辑以获取更多信息和可能的解决方案
我们最近修改了一个大型 Delphi 应用程序以使用 ADO 连接和查询,而不是 BDE 连接和查询。自那次更改以来,性能变得很糟糕。
我已经对应用程序进行了分析,瓶颈似乎在于对TADOQuery.Open
的实际调用。换句话说,从代码的角度来看,除了重构应用程序以减少对数据库的使用之外,我无能为力。
是否有人对如何提高与 ADO 连接的 Delphi 应用程序的性能提出建议? suggestions given here 两个我都试过了,几乎没有影响。
为了了解性能差异,我对相同的大型操作进行了基准测试:
在 BDE 下:11 秒
在 ADO 下:73 秒
在 ADO 下该文章引用的更改后:72 秒
我们在客户端-服务器环境中使用 Oracle 后端。每个本地机器都与数据库保持单独的连接。
作为记录,连接字符串如下所示:
const
c_ADOConnString = 'Provider=OraOLEDB.Oracle.1;Persist Security Info=True;' +
'Extended Properties="plsqlrset=1";' +
'Data Source=DATABASE.DOMAIN.COM;OPTION=35;' +
'User ID=******;Password=*******';
回答zendar提出的问题:
我在 Windows Vista 和 XP 上使用 Delphi 2007。
后端是Oracle 10g 数据库。
如连接字符串所示,我们使用的是 OraOLEDB 驱动程序。
我的基准机器上的 MDAC 版本是 6.0。
编辑:
在 BDE 下,我们有很多看起来像这样的代码:
procedure MyBDEProc;
var
qry: TQuery;
begin
//fast under BDE, but slow under ADO!!
qry := TQuery.Create(Self);
try
with qry do begin
Database := g_Database;
Sql.Clear;
Sql.Add('SELECT');
Sql.Add(' FIELD1');
Sql.Add(' ,FIELD2');
Sql.Add(' ,FIELD3');
Sql.Add('FROM');
Sql.Add(' TABLE1');
Sql.Add('WHERE SOME_FIELD = SOME_CONDITION');
Open;
//do something
Close;
end; //with
finally
FreeAndNil(qry);
end; //try-finally
end; //proc
但是我们发现在ADO下调用Sql.Add
实际上是非常昂贵的,因为每次更改CommandText
都会触发QueryChanged
事件。所以用这个替换上面的内容要快得多:
procedure MyADOProc;
var
qry: TADOQuery;
begin
//fast(er) under ADO
qry := TADOQuery.Create(Self);
try
with qry do begin
Connection := g_Connection;
Sql.Text := ' SELECT ';
+ ' FIELD1 '
+ ' ,FIELD2 '
+ ' ,FIELD3 '
+ ' FROM '
+ ' TABLE1 '
+ ' WHERE SOME_FIELD = SOME_CONDITION ';
Open;
//do something
Close;
end; //with
finally
FreeAndNil(qry);
end; //try-finally
end; //proc
更好的是,您可以从 ADODB.pas 中复制 TADOQuery
,将其重命名为新名称,然后删除 QueryChanged
事件,据我所知,这根本没有做任何有用的事情。然后使用新的、修改后的 TADOQuery 版本,而不是本机版本。
type
TADOQueryTurbo = class(TCustomADODataSet)
private
//
protected
procedure QueryChanged(Sender: TObject);
public
FSQL: TWideStrings;
FRowsAffected: Integer;
function GetSQL: TWideStrings;
procedure SetSQL(const Value: TWideStrings);
procedure Open;
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function ExecSQL: Integer; for TQuery compatibility
property RowsAffected: Integer read FRowsAffected;
published
property CommandTimeout;
property DataSource;
property EnableBCD;
property ParamCheck;
property Parameters;
property Prepared;
property SQL: TWideStrings read FSQL write SetSQL;
end;
////////////////////////////////////////////////////////
////////////////////////////////////////////////////////
////////////////////////////////////////////////////////
constructor TADOQueryTurbo.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FSQL := TWideStringList.Create;
TWideStringList(FSQL).OnChange := QueryChanged;
Command.CommandText := 'SQL'; Do not localize
end;
destructor TADOQueryTurbo.Destroy;
begin
inherited;
inherited Destroy;
FreeAndNil(FSQL);
end;
function TADOQueryTurbo.ExecSQL: Integer;
begin
CommandText := FSQL.Text;
inherited;
end;
function TADOQueryTurbo.GetSQL: TWideStrings;
begin
Result := FSQL;
end;
procedure TADOQueryTurbo.Open;
begin
CommandText := FSQL.Text;
inherited Open;
end;
procedure TADOQueryTurbo.QueryChanged(Sender: TObject);
begin
// if not (csLoading in ComponentState) then
// Close;
// CommandText := FSQL.Text;
end;
procedure TADOQueryTurbo.SetSQL(const Value: TWideStrings);
begin
FSQL.Assign(Value);
CommandText := FSQL.Text;
end;
【问题讨论】:
这个“OPTION=35”是干什么用的? 我不知道;它是由连接字符串生成器自动生成的,现在我自己很好奇.... "根本没有做任何有用的事情" 其实目的是解析脚本找到参数,填充参数结构。 @JerryDodge 好的,很高兴知道,它解释了为什么它有点耗时。删除它似乎对 ADO 转换没有太大的伤害,尽管我在这个距离上的记忆很模糊。我想念德尔福... 【参考方案1】:我不了解 Delphi 2007,但我对 Delphi 7 和 Oracle 8 做了同样的事情。
这是我做过的事情:
根据查询设置TAdoDataSet.CursorLocation: clUseClient 如果查询获取 GUI 记录并且查询相对“简单” - 没有分组或求和 clUseServer 如果查询有某种聚合(求和、分组、计数) 根据查询设置TAdoDataSet.CursorType: ctForwardOnly 适用于不需要回滚数据集的报告 - 仅适用于 clUseServer ctStatic 用于 GUI。这是唯一适用于 clUseClient 的模式 根据查询设置TAdoDataSet.LockType: ltReadOnly 适用于每个不用于编辑的数据集(表格、报告) ltOptimistic 当记录在更改后立即发布到数据库时(例如,用户在表单上编辑数据) ltBatchOptimistic 当您更改大量记录时。这适用于您获取记录数,然后对它们进行一些处理,然后将更新批量发送到数据库的情况。这与 clUseClient 和 ctStatic 结合使用效果最佳。 根据我的经验,适用于 Oracle 的 Microsoft OLEDB 提供程序比 Oracle OleDb 提供程序工作得更好。你应该测试一下。 编辑:查看 Fabricio 关于可能出现的 blob 问题的评论。 将 TAdoQUery 替换为 TAdoDataSet。 TAdoQuery 是为将应用程序从 BDE 转换为 ADO 而创建的,但 Borland/Codegear 建议使用 TAdoDataSet 重新检查 Oracle 连接字符串以确保您没有网络延迟。连接到 Oracle 需要多长时间? TnsPing 需要多长时间?【讨论】:
Microsoft OLEDB Provider for Oracle 如果表没有 blob 字段,则效果很好。否则,它简单地爆炸了一个例外。 Fabricio,您能说出哪个版本的 Oracle 和 MS OleDB 提供程序的行为如此吗? OLEDB:WinXP自带的Oracle:如果我的记忆不坏,Oracle 10 关于 BLOB,Oracle OleDB 提供程序似乎没有返回所有 BLOB 行(仅大约 25%)...见orafaq.com/forum/t/38121/0 和***.com/questions/6147274/… FWIW 在所有这些建议中,我发现将 TADODateSet.LockType 设置为 ltReadOnly 使我的查询速度提高了大约 10 倍。这是在 Firebird 数据库中。【参考方案2】:几年前我发现了 ADOExpress 的性能问题:
ADO vs ADOExpress time trials. Not good for ADOExpress (6/7/2005) ADO vs ADO Express Time Trials (redux) (12/30/2007)注意:在 ADO 成为 Delphi 的标准部分之前,Borland 将其作为名为 ADOExpress 的插件出售。它只是 Microsoft 的 ActiveX 数据对象 (ADO) COM 对象的对象包装器。
我测试了三个场景
直接使用 ADO(即直接使用 Microsoft 的 COM 对象) 使用 ADOExpress(Borland 的 ADO 对象包装器) 在调用Open
之前在TADOQuery
上指定.DisableControls
我发现
使用Query.DisableControls
使每个呼叫.Next
快50 倍
使用Query.Recordset.Fields.Items['columnName'].Value
而不是Query.FieldByName('columnName')
使每个值查找速度提高2.7 倍
使用TADODataSet
(诗句TADOQuery
)没有区别
Loop Results Get Values
ADOExpress: 28.0s 46.6s
ADOExpress w/DisableControls: 0.5s 17.0s
ADO (direct use of interfaces): 0.2s 4.7s
注意:这些值用于循环 20,881 行,并查找 21 列的值。
基准错误代码:
var
qry: TADOQuery;
begin
qry := TADOQuery.Create(nil);
try
qry.SQL.Add(CommandText);
qry.Open;
while not qry.EOF do
begin
...
qry.Next;
end;
使用 DisableControls 使循环速度提高 5000%:
var
qry: TADOQuery;
begin
qry := TADOQuery.Create(nil);
try
qry.DisableControls;
qry.SQL.Add(CommandText);
qry.Open;
while not qry.EOF do
begin
...
qry.Next;
end;
使用 Fields 集合将值查找速度提高 270%:
var
qry: TADOQuery;
begin
qry := TADOQuery.Create(nil);
try
qry.DisableControls;
qry.SQL.Add(CommandText);
qry.Open;
while not qry.EOF do
begin
value1 := VarAsString(qry.Recordset.Fields['FieldOne'].Value);
value2 := VarAsInt(qry.Recordset.Fields['FieldTwo'].Value);
value3 := VarAsInt64(qry.Recordset.Fields['FieldTwo'].Value);
value4 := VarAsFloat(qry.Recordset.Fields['FieldThree'].Value);
value5 := VarAsWideString(qry.Recordset.Fields['FieldFour'].Value);
...
value56 := VarAsMoney(qry.Recordset.Fields['FieldFive'].Value);
qry.Next;
end;
由于这是一个很常见的问题,我们创建了一个辅助方法来解决这个问题:
class function TADOHelper.Execute(const Connection: TADOConnection;
const CommandText: WideString): TADOQuery;
var
rs: _Recordset;
query: TADOQuery;
nRecords: OleVariant;
begin
Query := TADOQuery.Create(nil);
Query.DisableControls; //speeds up Query.Next by a magnitude
Query.Connection := Connection;
Query.SQL.Text := CommandText;
try
Query.Open();
except
on E:Exception do
begin
Query.Free;
raise;
end;
end;
Result := Query;
end;
【讨论】:
什么时候重新启用控件?我总是在禁用控件时使用 try ... finally 块,以确保无论发生什么都重新启用控件(数据网格)。 @No'amNewman 我们从不使用数据绑定控件。曾经。自己做所有事情变得更快更容易。 我不得不不同意你的观点。我写了一些非绑定对话框;有些很容易写,但有些很难写,从而增加了出错或遗漏的机会。【参考方案3】:为了获得最佳性能,请查看我们的Open Source direct access to Oracle。
如果你要处理大量的 TQuery,而不使用 DB 组件,我们有一个专门的伪类来使用直接 OCI 连接,例如:
Q := TQuery.Create(aSQLDBConnection);
try
Q.SQL.Clear; // optional
Q.SQL.Add('select * from DOMAIN.TABLE');
Q.SQL.Add(' WHERE ID_DETAIL=:detail;');
Q.ParamByName('DETAIL').AsString := '123420020100000430015';
Q.Open;
Q.First; // optional
while not Q.Eof do begin
assert(Q.FieldByName('id_detail').AsString='123420020100000430015');
Q.Next;
end;
Q.Close; // optional
finally
Q.Free;
end;
我通过后期绑定变体添加了一些独特的访问权限,以编写直接代码:
procedure Test(Props: TOleDBConnectionProperties; const aName: RawUTF8);
var I: ISQLDBRows;
Customer: Variant;
begin
I := Props.Execute('select * from Domain.Customers where Name=?',[aName],@Customer);
while I.Step do
writeln(Customer.Name,' ',Customer.FirstName,' ',Customer.Address);
end;
var Props: TOleDBConnectionProperties;
begin
Props := TSQLDBOracleConnectionProperties.Create(
'TnsName','UserName','Password',CODEPAGE_US);
try
Test(Props,'Smith');
finally
Props.Free;
end;
end;
请注意,所有 OleDB 提供程序在处理 BLOB 时都有缺陷:Microsoft 的版本只是不处理它们,而 Oracle 的版本将 randomly return null for 1/4 of rows...
在真实数据库上,我发现我们的直接 OCI 类比 OleDB 提供程序快 2 到 5 倍,而无需安装此提供程序。您甚至可以使用 Oracle 提供的Oracle Instant Client,它允许您在不安装标准(大型)Oracle 客户端或拥有 ORACLE_HOME 的情况下运行应用程序。只需将 dll 文件与您的应用程序放在同一目录中即可。
【讨论】:
我刚刚添加了批量添加,即数组绑定。现在插入的性能也很棒!每秒 55,000 行用于插入,每秒 90,000 行用于读取。见blog.synopse.info/post/2012/07/25/Synopse-mORMot-benchmark以上是关于Delphi 中的 BDE 与 ADO的主要内容,如果未能解决你的问题,请参考以下文章
急!!在delphi中如何将sql server 2000数据库中的一个表中的内容全部导到excel表中
delphi 怎么连接oracle数据库,并做增,删,改,查等操作.