Delphi中的BDE与ADO

Jos*_*ons 13 delphi oracle performance ado bde

请注意下面的编辑以获取更多信息,以及可能的解决方案

我们最近修改了一个大型Delphi应用程序,以使用ADO连接和查询而不是BDE连接和查询.自那次改变以来,表现变得非常糟糕.

我已经分析了应用程序,瓶颈似乎是在实际调用TADOQuery.Open.换句话说,除了重构应用程序以减少实际使用数据库之外,从代码角度来看,我无法做很多事情来改进这一点.

有没有人有关于如何提高ADO连接的Delphi应用程序性能的建议?我已经尝试了这里给出的两个建议,几乎没有任何影响.

为了了解性能差异,我对相同的大型操作进行了基准测试:

  • 在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=*******';
Run Code Online (Sandbox Code Playgroud)

回答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
Run Code Online (Sandbox Code Playgroud)

但我们发现Sql.Add在ADO下调用实际上非常昂贵,因为QueryChanged每次更改时都会触发事件CommandText.所以用这个代替上面的东西要快得多:

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
Run Code Online (Sandbox Code Playgroud)

更好的是,您可以复制TADOQuery出ADODB.pas,以新名称重命名,然后删除该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;
Run Code Online (Sandbox Code Playgroud)

zen*_*dar 14

我不知道Delphi 2007,但我对Delphi 7和Oracle 8做了同样的事情.

这是我做的事情:

  • 根据查询设置TAdoDataSet.CursorLocation:
    • clUseClient如果查询获取GUI的记录并且查询相对"简单" - 没有分组或总和
    • clUseServer如果查询有某种聚合(求和,分组,计数)
  • 根据查询设置TAdoDataSet.CursorType:
    • ctForwardOnly用于不需要向后滚动数据集的报表 - 仅适用于 clUseServer
    • 用于GUI的 ctStatic.这只是与 clUseClient一起使用的模式
  • 根据查询设置TAdoDataSet.LockType:
    • ltReadOnly用于未用于编辑的每个数据集(网格,报告)
    • 当记录在更改后立即发布到数据库时(例如用户在表单上编辑数据),显示优先级
    • 更改大量记录时的 ltBatchOptimistic.这适用于您获取记录数,然后对它们进行一些处理然后批量发送更新到数据库的情况.这最好与clUseClient和ctStatic结合使用.
  • 根据我的经验,Oracle的Microsoft OLEDB提供商比Oracle OleDb提供商工作得更好.你应该测试一下.
    编辑:检查Fabricio关于可能的blob问题的评论.
  • 更换TAdoQUeryTAdoDataSet.TAdoQuery是为将应用程序从BDE转换为ADO而创建的,但Borland/Codegear推荐是使用TAdoDataSet
  • 重新检查Oracle连接字符串以确保您没有网络延迟.连接到Oracle需要多长时间?TnsPing有多长时间?


Ian*_*oyd 5

几年前我发现了 ADOExpress 的性能问题:

注意:在 ADO 成为 Delphi 的标准部分之前,Borland 将其作为名为ADOExpress的插件出售。它只是 Microsoft 的 ActiveX 数据对象 (ADO) COM 对象的对象包装器。

我测试了三个场景

  • 直接使用 ADO(即直接使用微软的 COM 对象)
  • 使用 ADOExpress(Borland 围绕 ADO 的对象包装器)
  • 指定.DisableControlsTADOQuery之前调用Open

我发现

  • 用于Query.DisableControls使每次调用.Next速度提高 50 倍
  • 使用Query.Recordset.Fields.Items['columnName'].Value而不是Query.FieldByName('columnName')使每个值查找速度提高 2.7 倍
  • 使用TADODataSet(verses 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 
    
    Run Code Online (Sandbox Code Playgroud)

注意:这些值用于循环 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;
Run Code Online (Sandbox Code Playgroud)

使用 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;
Run Code Online (Sandbox Code Playgroud)

使用 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;
Run Code Online (Sandbox Code Playgroud)

由于这是一个足够常见的问题,我们创建了一个辅助方法来解决这个问题:

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;
Run Code Online (Sandbox Code Playgroud)