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:
- ADO vs ADOExpress pruebas de tiempo. No es bueno para ADOExpress (6/7/2005)
- ADO vs ADO Pruebas de tiempo expreso (redux) (30/12/2007)
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
enTADOQuery
antes de llamar aOpen
yo descubrí
- use
Query.DisableControls
para hacer cada llamada.Next
más rápido - use
Query.Recordset.Fields.Items[''columnName''].Value
lugar deQuery.FieldByName(''columnName'')
para que cada búsqueda de valores seaQuery.FieldByName(''columnName'')
más rápida usar
TADODataSet
(versosTADOQuery
) no hace la diferenciaLoop 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á.