performance oracle delphi ado bde

performance - BDE vs ADO en Delphi



oracle (3)

No sé sobre Delphi 2007, pero hice lo mismo con Delphi 7 y Oracle 8.

Aquí hay cosas que hice:

  • Establezca TAdoDataSet.CursorLocation según la consulta:
    • clUseClient si la consulta recupera registros para la GUI y la consulta es relativamente "simple" - sin agrupar ni sumar
    • clUseServer si la consulta tiene algún tipo de agregación (suma, agrupación, conteo)
  • Establezca TAdoDataSet.CursorType según la consulta:
    • ctForwardOnly para informes en los que no necesita desplazarse hacia atrás a través del conjunto de datos: funciona solo con clUseServer
    • ctStatic para GUI. Este es solo el modo que funciona con clUseClient
  • Establezca TAdoDataSet.LockType según la consulta:
    • Léalo solo para cada conjunto de datos que no se usa para la edición (grillas, informes)
    • Óptimo cuando los registros se publican en la base de datos inmediatamente después del cambio (por ejemplo, los datos de edición del usuario en el formulario)
    • ltBatchOptimistic cuando cambia gran cantidad de registros. Esto se aplica a situaciones en las que se obtiene el número de registros, luego se procesa y luego se envían actualizaciones a la base de datos por lotes. Esto funciona mejor combinado con clUseClient y ctStatic.
  • En mi experiencia, el proveedor OLEDB de Microsoft para Oracle funcionó mejor que el proveedor Oracle OleDb. Deberías probar eso.
    Editar: consulta el comentario de Fabricio sobre posibles problemas de blobs.
  • Reemplace TAdoQUery con TAdoDataSet . TAdoQuery fue creado para la conversión de aplicaciones de BDE a ADO, pero la recomendación de Borland / Codegear fue usar TAdoDataSet
  • Vuelva a verificar la cadena de conexión de Oracle para asegurarse de que no tiene latencia de red. ¿Cuánto tiempo dura conectarse a Oracle? ¿Cuánto dura TnsPing?

Tenga en cuenta la siguiente edición para obtener mucha más información y una posible solución

Recientemente modificamos una gran aplicación Delphi para usar conexiones y consultas ADO en lugar de consultas y conexiones BDE. Desde ese cambio, el rendimiento se ha vuelto terrible.

He perfilado la aplicación y el cuello de botella parece estar en la llamada real a TADOQuery.Open . En otras palabras, no hay mucho que pueda hacer desde el punto de vista del código para mejorar esto, aparte de reestructurar la aplicación para que realmente use menos la base de datos.

¿Alguien tiene sugerencias sobre cómo mejorar el rendimiento de una aplicación Delphi conectada a ADO? He intentado ambas sugerencias aquí , prácticamente sin impacto.

Para dar una idea de la diferencia de rendimiento, comparé la misma gran operación:

  • En BDE: 11 segundos

  • Bajo ADO: 73 segundos

  • Bajo ADO después de los cambios a los que hace referencia ese artículo: 72 segundos

Estamos utilizando un back-end de Oracle en un entorno cliente-servidor. Las máquinas locales mantienen una conexión separada a la base de datos.

Para el registro, la cadena de conexión se ve así:

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=*******'';

Para responder a las preguntas planteadas por zendar:

Estoy usando Delphi 2007 en Windows Vista y XP.

El back-end es una base de datos Oracle 10g.

Como lo indica la cadena de conexión, estamos usando el controlador OraOLEDB.

La versión de MDAC en mi máquina de referencia es 6.0.

Editar:

Bajo el BDE, teníamos un montón de código que se veía así:

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

Pero descubrimos que la llamada a Sql.Add es realmente muy costosa bajo ADO, porque el evento QueryChanged se QueryChanged cada vez que cambia el CommandText . Así que reemplazar lo anterior con esto fue MUCHO más rápido:

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

Mejor aún, puede copiar TADOQuery de ADODB.pas, cambiarle el nombre con un nuevo nombre y extraer el evento QueryChanged , que hasta donde yo sé, no está haciendo nada útil en absoluto. Luego use su nueva versión modificada de TADOQuery, en lugar de la versión nativa.

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;


encontré los problemas de rendimiento con ADOExpress hace años:

Nota: Antes de que ADO se convirtiera en una parte estándar de Delphi, Borland lo vendía como un complemento llamado ADOExpress . Era simplemente envoltorios de objetos alrededor de los objetos COM ActiveX Data Objects (ADO) de Microsoft.

había probado tres escenarios

  • utilizando ADO directamente (es decir, los objetos COM de Microsoft directamente)
  • utilizando ADOExpress (envoltorios de objetos Borland alrededor de ADO)
  • especificando .DisableControls en TADOQuery antes de llamar a Open

yo descubrí

  • use Query.DisableControls para hacer cada llamada .Next más rápido
  • use Query.Recordset.Fields.Items[''columnName''].Value lugar de Query.FieldByName(''columnName'') para que cada búsqueda de valores sea Query.FieldByName(''columnName'') más rápida
  • usar TADODataSet (versos TADOQuery ) no hace la diferencia

    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

Nota : Estos valores son para recorrer 20,881 filas y buscar los valores de 21 columnas.

Código de Baseline Bad:

var qry: TADOQuery; begin qry := TADOQuery.Create(nil); try qry.SQL.Add(CommandText); qry.Open; while not qry.EOF do begin ... qry.Next; end;

Use DisableControls para hacer el bucle 5000% más rápido :

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;

Use la colección Fields para realizar búsquedas de valor un 270% más rápido :

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;

Dado que es un problema bastante común, creamos un método de ayuda para resolver el problema:

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;


Para obtener el mejor rendimiento, debe echar un vistazo a nuestro acceso directo de código abierto a Oracle .

Si está procesando una gran cantidad de TQuery, sin usar los componentes DB, tenemos una pseudo-clase dedicada para usar la conexión OCI directa, como tal:

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;

Y he agregado un acceso único a través de una Variante de enlace tardío, para escribir código directo como tal:

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;

Tenga en cuenta que todos los proveedores de OleDB tienen errores para manejar BLOB: la versión de Microsoft simplemente no los maneja, y la versión de Oracle devolverá aleatoriamente null para 1/4 de filas ...

En una base de datos real, descubrí que nuestras clases directas de OCI eran de 2 a 5 veces más rápidas que el proveedor de OleDB, sin la necesidad de instalar este proveedor. Incluso puede utilizar Oracle Instant Client proporcionado por Oracle, que le permite ejecutar sus aplicaciones sin instalar el cliente Oracle estándar (enorme) o tener un ORACLE_HOME. Simplemente entregue los archivos dll en el mismo directorio que su aplicación, y funcionará.