También quedó muy claro que la velocidad de ejecución de consultas con este componente respecto a otros como IBQuery es muy superior. Todo lo que hemos visto esta bien para hacer consultas esporádicas sobre alguna tabla que otra, pero ¿que ocurre si tenemos que realizar miles de consultas SQL de una sola vez?
UTILIZANDO UNA TRANSACCION POR CONSULTA
Supongamos que tenemos que modificar el nombre de 1000 registros de la tabla CLIENTES:
var
i: Integer;
dwTiempo: DWord;
begin
with Consulta do
begin
////////////// METODO LENTO ////////////////
dwTiempo := TimeGetTime;
for i := 1 to 1000 do
begin
SQL.Clear;
SQL.Add( 'UPDATE CLIENTES' );
SQL.Add( 'SET NOMBRE = ' + QuotedStr( 'NOMBRE CLIENTE Nº ' + IntToStr( i ) ) );
SQL.Add( 'WHERE ID = ' + IntToStr( i ) );
Transaction.StartTransaction;
try
ExecQuery;
Transaction.Commit;
except
on E: Exception do
begin
Application.MessageBox( PChar( E.Message ), 'Error de SQL', MB_ICONSTOP );
Transaccion.Rollback;
end;
end;
end;
ShowMessage( 'Tiempo: ' + IntToStr( TimeGetTime - dwTiempo ) + ' milisegundos' );
end;
end;
Como puede verse arriba, por cada cliente actualizado he generado una SQL distinta abriendo y cerrando una transacción para cada registro. He utilizado la función TimeGetTime que se encuentra en la unidad MMSystem para calcular el tiempo que tarda en actualizarme el nombre de los 1000 clientes. En un PC con Pentium 4 a 3 Ghz, 1 GB de RAM y utilizando el motor de bases de datos Firebird 2.0 me ha tardado 4167 milisegundos.
Aunque las consultas SQL van muy rápidas con los componentes IBSQL aquí el fallo que cometemos es que por cada registro actualizado se abre y se cierra una transacción. En una base de datos local no se nota mucho pero en una red local con muchos usuarios trabajando a la vez le puede pegar fuego al concentrador.
Lo ideal sería poder modificar la SQL pero sin tener que cerrar la transacción. Como eso no se puede hacer en una consulta que esta abierta entonces hay que utilizar los parámetros. Los parámetros (Params) nos permiten enviar y recoger información de una consulta SQL que se esta ejecutando sin tener que cerrarla y abrila.
UTILIZANDO PARAMETROS EN LA CONSULTA
Para introducir parámetros en una consulta SQL hay que añadir dos puntos delante del parámetro. Por ejemplo:
UPDATE CLIENTES
SET NOMBRE = :NOMBRE
WHERE ID = :ID
Esta consulta tiene dos parámetros: ID y NOMBRE. Los nombres de los parámetros no tienen porque coincidir con el nombre del campo. Bien podrían ser así:
UPDATE CLIENTES
SET NOMBRE = :NUEVONOMBRE
WHERE ID = :IDACTUAL
De este modo se pueden modificar las condiciones de la consulta SQL sin tener que cerrar la transacción. Después de crear la consulta SQL hay que llamar al método Prepare para que prepare la consulta con los futuros parámetros que se le van a suministrar (no es obligatorio pero si recomendable). Veamos el ejemplo anterior utilizando parámetros y una sóla transacción para los 1000 registros:
var
i: Integer;
dwTiempo: DWord;
begin
with Consulta do
begin
////////////// METODO RÁPIDO ////////////////
dwTiempo := TimeGetTime;
Transaction.StartTransaction;
SQL.Clear;
SQL.Add( 'UPDATE CLIENTES' );
SQL.Add( 'SET NOMBRE = :NOMBRE' );
SQL.Add( 'WHERE ID = :ID' );
Prepare;
for i := 1 to 1000 do
begin
Params.ByName( 'NOMBRE' ).AsString := 'NOMBRE CLIENTE Nº '+ IntToStr( i );
Params.ByName( 'ID' ).AsInteger := i;
ExecQuery;
end;
try
Transaction.Commit;
except
on E: Exception do
begin
Application.MessageBox( PChar( E.Message ), 'Error de SQL', MB_ICONSTOP );
Transaccion.Rollback;
end;
end;
ShowMessage( 'Tiempo: ' + IntToStr( TimeGetTime - dwTiempo ) + ' milisegundos' );
end;
end;
En esta ocasión me ha tardado sólo 214 milisegundos, es decir, se ha reducido al 5% del tiempo anterior sin saturar al motor de bases de datos abriendo y cerrando transacciones sin parar.
Este método puede aplicarse también para consultas con INSERT, SELECT y DELETE. En lo único en lo que hay que tener precaución es en no acumular muchos datos en la transacción, ya que podría ser peor el remedio que la enfermedad.
Si teneis que actualizar cientos de miles de registros de una sola vez, recomiendo realizar un Commit cada 1000 registros para no saturar la memoria caché de la transacción. Todo depende del número de campos que tengan las tablas así como el número de registros a modificar. Utilizad la función TimeGetTime para medir tiempos y sacar conclusiones.
Y si el proceso a realizar va a tardar más de 2 o 3 segundos utilizar barras de progreso e hilos de ejecución, ya que algunos usuarios neuróticos podrían creer que nuestro programa se ha colgado y empezarían ha hacer clic como posesos (os aseguro que existe gente así, antes de que termine la consulta SQL ya te están llamando por teléfono echándote los perros).
Pruebas realizadas con Firebird 2.0 y Dephi 7.