Páginas

segunda-feira, 27 de setembro de 2010

Lazarus - AutoCommit no ZeosLib com PostgreSQL

Por padrão, o PostgreSQL trata cada comando SQL como sendo executado dentro de uma transação. Ou seja, cada comando DML (INSERT, UPDATE ou DELETE) tem um comando de início de transação (BEGIN) e um comando COMMIT – caso seja bem sucedido – executados implicitamente antes e depois. Para a execução de múltiplos comandos DML isso leva a um overhead. Desta forma, sugere-se fortemente envolver todos os comandos DML em uma única transação – se a lógica da sua aplicação permitir. Para demonstrar esta recomendação fizemos um teste simples. Escrevemos um pequeno programa para inserir 5.000 linhas em uma tabela. Primeiro fizemos o teste com o controle de transação padrão - cada uma das 5.000 linhas tem seu próprio BEGIN e COMMIT - e depois modificamos o código para controlar a transação manualmente. Neste último teste todas as 5.000 linhas são inseridas dentro de uma única transação.
Para nosso teste usamos ZeosLib. Por padrão o ZeosLib também opera no modo Auto-Commit, ou seja, cada comando é executado no contexto de uma transação. Não mostraremos aqui como conectar ao PG usando Zeos, pois isso já foi apresentado no post Lazarus - Conectando PostgreSQL com ZeosLib. Vejamos o primeiro teste. A unit Dateutils deve ser incluida na cláusula uses para poder usar a função
MilliSecondsBetween(). dbBanco é um ZConnection, dmDados é um Data Module e queCidade uma ZQuery.

var
  i: integer;
  a, b: TDateTime;
begin
  dmDados.queCidade.SQL.Clear;
  dmDados.queCidade.SQL.Add('insert into cidade values (:id, :nome)');
  a := time;
  for i := 1 to 5000 do
  begin
    dmDados.queCidade.ParamByName('ID').Value:= i;
    dmDados.queCidade.ParamByName('NOME').Value:='SANTAREM';
    dmDados.queCidade.ExecSql;
  end;
  b := time;
  ShowMessage(IntToStr(MilliSecondsBetween(b,a)));
end;

Neste primeiro código o tempo médio de inserção das 5.000 linhas foi de 11,6 segundos. Alteramos então nosso código para que todas as linhas fossem inseridas dentro de uma única transação. Para isso chamamos StartTransaction antes de iniciar os INSERTs. Observe o código abaixo.

var
  i: integer;
  a, b: TDateTime;
begin
  a := time;
  dmDados.queCidade.SQL.Clear;
  dmDados.queCidade.SQL.Add('insert into cidade values (:id, :nome)');
  dmDados.dbBanco.StartTransaction;
  for i := 1 to 5000 do
  begin
    dmDados.queCidade.ParamByName('ID').Value:= i;
    dmDados.queCidade.ParamByName('NOME').Value:='SANTAREM';
    dmDados.queCidade.ExecSql;
  end;
  dmDados.dbBanco.Commit;
  b := time;
  ShowMessage(IntToStr(MilliSecondsBetween(b,a)));
end;

Neste caso o tempo médio de execução do código foi de 2,9 segundos. Um ganho de tempo considerável em relação à execução em modo padrão.
Fizemos também um teste usando TZUpdateSQL, implementado de acordo com o post citado acima. O código é apresentado em seguida e o tempo médio de execução foi de 3,8 segundos. Um desempenho um pouco pior do que o teste anterior.

var
  i: integer;
  a, b: TDateTime;
begin
  a := time;
  dmDados.queCidade.Open;
  dmDados.dbBanco.StartTransaction;
  for i := 1 to 5000 do
  begin
    dmDados.queCidade.Insert;
    dmDados.queCidade.FieldByName('ID').Value := i;
    dmDados.queCidade.FieldByName('NOME').Value:='SANTAREM';
    dmDados.queCidade.Post;;
  end;
  dmDados.dbBanco.Commit;
  b := time;
  ShowMessage(IntToStr(MilliSecondsBetween(b,a)));
end;

Estes teste foram feitos em Windows e servidor de BD e cliente rodando na mesma máquina. Certamente se você executar esses testes em outro ambiente, os tempos serão diferentes, mas comparativamente você deverá chegar à mesma conclusão.
Sabe-se que uma falha no sistema, enquanto uma transação estiver ativa, causa a execução de um ROLLBACK pelo SGBD, fazendo com que todas as atualizações feitas a partir do BEGIN sejam canceladas. Portanto, use esta recomendação com cautela e bom senso. Longas transações ativas estão sujeitas a perdas de dados muito maiores que transações curtas, em caso de falhas no sistema.

Atualizado em 28/09/2010.

13 comentários:

Anônimo disse...

Professor, interessante este artigo sobre transação usando ZeosLib e PGSql.
Estou tentando usar o mesmo conceito usando SQLdb e MySql, porém não tenho sucesso, é como se a transação não tivesse sido inicializada. O sr. sabe me dizer se é possível?

Professor Carlos disse...

No SQLdb tem o SQLTransaction. É ele que chama o StartTransaction e o Commit. Fiz o teste aqui e funcionou.

Anônimo disse...

Ok, Obrigado.
No meu caso estou usando Linux. Você conhece algum caso? A versão de Lazarus que estou usando pode influenciar nessa questão?

Professor Carlos disse...

Estou usando a versão 0.9.29 do Lazarus. E penso que não teria diferença no caso de ser Linux.

Anônimo disse...

Valeu Professor, obrigado pela força.

Anônimo disse...

Professor, quero aproveitar, e parabenizá-lo pela iniciativa. Eu também estou começando a minha aventura com a Lazarus, e vejo bastante futuro com esse. E os seus artigos estão sendo de grande valia para mim, e pretendo no futuro dar minha parcela de colaboração.
Até mais, obrigado.

Professor Carlos disse...

Obrigado pelo comentário. Quanto mais pessoas usarem o Lazarus, maiores serão as chances de seu crescimento.

Anônimo disse...

Oi professor, bom dia!
Me parece que no windows mobile a dll sqllite não é muito confiável, pois
em um mesmo aplicativo, estava funcionando sem problemas, porém sem nenhuma alteração passou a não conectar mais a base de dados.
Teria alguma informação sobre essa situacao?

Professor Carlos disse...

Eu também tive dificuldades com a dll no WM 6. No WM 5 está funcionando bem. Nesse link http://www.parmaja.com/downloads/sqlite3-3.7.0.1-wince-arm.zip
Eles dizem que tem uma nova versão da dll. Seria o caso de fazer testes. Eu não tive tempo de fazer isso.

Rafael Elias disse...

Caro Professor

Em um sistema que desenvolvi, em algumas situações precisei controlar as transações manualmente, pois eu necessitava gravar dois blocos de informações, e caso o segundo falhasse, o primeiro deveria ser revertido. Ótimo, criei uma rotina com TRY EXCEPT END, com StartTransaction, Commit e RollBack e setei o autocommit do ZConnection para false. Porém quando executava a rotina recebia um erro mais ou menos assim: "Cannot perform this operation in non autocommit connection".
Após pesquisar no site do Zeoslib, encontrei a informação que eu precisava. Lá eu li que eu não precisava usar o starttransaction, que este era chamado automaticamente, e que eu precisaria apenas do commit ou rollback. Testei e funcionou perfeito, até simulei um erro para confirmar que o rollback também funcionava, e funcionou.

Uso Lazarus 0.9.31 com Zeos 7.0 ambos do SVN.

Gostaria de aproveitar para parabenizá-lo pelo blog e pela divulgação do Lazarus.

Mantenho um site com informações, tutoriais, artigos, etc. para desenvolvimento de sistemas para WEB com Lazarus (lazarus-cgi.co.cc) todo feito em Lazarus, e gostaria de colocar um link do seu blog por lá. Caso ache a ideia interessante, me informe por favor [é gratis :)].

Abraço

Rafael Tuim Elias

Hildo disse...

Estava com problemas no desenvolvimento de uma apliação e sua explicação foi de grande valia.

Muito obrigado.

Fabio Luis Girardi disse...

Olá Professor!

Vi nos comentários que você utilizou SQLite com Windows Mobile 5 e 6 (WinCE). Estou desenvolvendo um projeto aberto que utiliza ZeosLib e estou pensando em adicionar suporte a Windows CE a ele. Pergunto: Você utilizou ZeosLib com Lazarus para isso?

Professor Carlos disse...

Oi Fábio

ZeosLib não funcionou para WinCE. Usei TSQLite3Dataset da unit sqlite3ds. Você pode ver como eu fiz em http://professorcarlos.blogspot.com/2010/03/lazarus-criando-uma-aplicacao-para.html. Fiz com DBF também: http://professorcarlos.blogspot.com/2010/08/lazarus-wince-com-dbf.html

 
Creative Commons License
This work by Carlos Alberto P. Araújo is licensed under a Creative Commons Atribuição-Uso não-comercial-Compartilhamento pela mesma licença 3.0 Brasil License.