Você está na página 1de 6

17/08/2020 Artigos

Lock de Registro em banco de dados Firebird

Introdução
 
Quando trabalhávamos com tabelas paradox nos acostumamos com o comportamento padrão do BDE de travar um
registro quando o mesmo já fosse colocado em edição por outro usuário do sistema ou mesmo outra aplicação. Assim que
começamos migrar nossas aplicações de tabelas paradox para um banco relacional, começamos a fazer comparações entre a
nossa antiga forma de gerenciar a base de dados e a nova que estaríamos começando a utilizar. Uma das primeiras observações
realizadas foi a detecção da falta de um travamento de registro automatizado pelo banco de dados. Embora muitos não tenham
o costume ou achem desnecessário o travamento de registro, em alguns casos especiais isto pode ser útil, quando um mesmo
registro pode ser concorrido por dois ou mais usuários para sua edição, ou em aplicações Web onde o numero escritas
concorrentes seja elevada. Neste artigo vou falar a respeito do travamento de registro ou lock de registro com banco de dados
Firebird usando DBExpress.
 
Procedimento Padrão (antes da versão Firebird 1.5)
 
Logo que o Firebird foi lançado ainda não tínhamos um controle especifico para “travar” um registro, apenas
poderíamos travar um determinado registro caso fizéssemos um update neste registro dentro de uma transação, onde devido
ao nível de isolamento usado nesta transação o registro não ficaria disponível para visualização. Como é esta técnica: faríamos
um update, um delete de um registro (optimistic locking) ou conjunto de registro (pessimistic locking) quando não se sabe ao
certo qual registro irá ser alterado, sendo que a instrução usada para travar um determinado registro seria:

UPDATE CUSTOMER SET CUST_NO = CUST_NO


WHERE CUST_NO = CUST_NO

 
Observe que neste caso não alteramos o valor do campo código, mas o registro entrou na transação uma vez que foi
executado o update sobre ele. Para liberar o registro ou um conjunto de registros basta executar o COMMIT ou ROLLBACK na
transação corrente.
 
Com cláusula WITH LOCK
 
Na versão 1.5 do firebird entre as diversas novidades que foram lançadas, uma delas foi a cláusula WITH LOCK, com
esta instrução podemos travar um conjunto de registros a partir de um simples select. Esta cláusula realiza um lock pessimista
explicito de registros, assim sugiro que sempre a use em conjunto com a cláusula WHERE a fim de filtrar a seleção de registros.
A seguir veja um exemplo de sua utilização: 

SELECT * FROM CUSTOMER


WHERE CUST_NO = 1001
WITH LOCK

 
Exemplo
 
Neste exemplo irei exemplificar o travamento de apenas um registro no momento de sua edição, trabalharemos com o
banco EMPLOYEE, mais precisamente com a tabela CUSTOMER, onde na edição do registro dos dados do cliente iremos travar o
registro para que nenhum outro usuário consiga editar o mesmo registro simultaneamente.
No Delphi crie uma nova aplicação e chame de LockRegistro.dpr, e um formulário principal que iremos salvar como
unPrincipal.pas,  alterando o nome do formulário para frmPrincipal, este formulário principal terá como objetivo apenas listar os
registros da tabela para sua seleção e edição. Neste formulário (Imagem 1) adicione e configure os seguintes componentes:
 

TSQLConnection
(propriedade=valor)
Name = sqlConexao
ConnectionName = 'LOCKREGISTRO'
DriverName = 'Interbase'
Params.Strings = (
'DriverName=Interbase'
'Database= EMPLOYEE.GDB'
'RoleName=RoleName'
'User_Name=sysdba'
'Password=masterkey'
'ServerCharSet='
'SQLDialect=3'
theclub.com.br/Restrito/Revistas/200901/lock0901.aspx 1/6
17/08/2020 Artigos
'BlobSize=-1'
'CommitRetain=False'
'WaitOnLocks=False'
'ErrorResourceFile='
'LocaleCode=0000'
'Interbase TransIsolation=ReadCommited'
'Trim Char=False')
Connected = True

TSQLDataSet
(propriedade=valor)
Name = sqlCustomers
CommandText = 'select * from CUSTOMER'
SQLConnection = sqlConexao

TDataSetProvider
(propriedade=valor)
Name = dspCustomers
DataSet = sqlCustomers

TClientDataSet
(propriedade=valor)
Name = cdsCustomers:
ProviderName = 'dspCustomers'
Active = True

TDataSource
(propriedade=valor)
Name = dsCustomers:
DataSet = cdsCustomers

TDBNavigator
(propriedade=valor)
DataSource = dsCustomers
VisibleButtons = [nbFirst, nbPrior, nbNext, nbLast, nbCancel, nbRefresh]

TDBGrid
(propriedade=valor)
Name = dbGridCustomers
DataSource = dsCustomers
ReadOnly = True

 
Nas configurações dos componentes listados acima, a mais importante no contexto deste artigo é a alteração do
parâmetro WaitOnLocks do TSQLConnection para False, este parâmetro tem como objetivo controlar a espera ou não da
aplicação quando lock de registro for identificado, ou seja com esta propriedade como True, quando um determinado registro
já locado for ser selecionado a aplicação irá ficar travada aguardando a liberação do registro, para que possa fazer o “fetch”
deste registro. Este é um comportamento desagradável que não desejamos em nossa aplicação, assim iremos alterar esta
propriedade para False, onde quando lock de registro for identificado, irá ser retornada uma mensagem de erro de deadlock
que iremos tratar.  Não se esqueça também de adicionar os TFields no ClientDataSet
 

theclub.com.br/Restrito/Revistas/200901/lock0901.aspx 2/6
17/08/2020 Artigos

Imagem 1
 
Agora crie um formulário que iremos usar para edição do registro e salve como unCustomer.pas e altere a propriedade
name do formulário para frmCustomer. Adicione nesta unit a referência da unit unPrincipal.pas com Alt+F11. Neste formulário
(Imagem 1) adicione e configure os seguintes componentes:
 

TSQLDataSet
(propriedade=valor)
Name = sdsCustomer
CommandText = 'select * from CUSTOMER'
SQLConnection = frmPrincipal.sqlConexao

TDataSetProvider
(propriedade=valor)
Name = dspCustomer
DataSet = sdsCustomer
Options = [poAllowCommandText]
UpdateMode = upWhereKeyOnly

TDataSource
(propriedade=valor)
dsCustomer
DataSet = cdsCustomer

TClientDataSet
(propriedade=valor)
cdsCustomer
ProviderName = 'dspCustomer'

 
Adicione também os campos (TFields) tanto no SqlDataset quanto no ClientDataset, e configure os ProviderFlags do
campo chave primária CUST_NO como:   [pfInUpdate, pfInWhere, pfInKey], e para os demais campos:   [pfInUpdate]. Também
coloque os respectivos DBEdit para cada campo e um DBNavigator.
 

theclub.com.br/Restrito/Revistas/200901/lock0901.aspx 3/6
17/08/2020 Artigos

Imagem 2
 
Como a idéia é no formulário principal ao dar um duplo-clique no DBgrid abrir a janela de edição (frmCustomers),
vamos adicionar o evento  DblClick no dbGridCustomers, não se esquecendo de adicionar a unit unCustomer.pas no uses da
unit principal, coloque a seguinte instrução :
 

implementation

uses unCustomer;

{$R *.dfm}

procedure TfrmPrincipal.dbGridCustomersDblClick(Sender: TObject);


begin
frmCustomer := TfrmCustomer.Create(Self, cdsCustomersCUST_NO.AsInteger);
frmCustomer.ShowModal;
frmCustomer.Free;
end;

end.

 
Dica: Observe que na criação do frmCustomer criamos um segundo parâmetro para informar para o formulário o valor
do campo CUST_NO.  Para isto faremos um overload do constructor da classe do formulário, sobrescrevendo o create do
constructor, onde neste evento iremos capturar o valor do parâmetro e atribuir para uma variável privada na mesma classe, veja
código abaixo o código correspondente:
 

{...}
private
_CUST_NO: Integer;
public
constructor Create(AOwner: TComponent; CUST_NO: Integer); overload;
end;

theclub.com.br/Restrito/Revistas/200901/lock0901.aspx 4/6
17/08/2020 Artigos

var
frmCustomer: TfrmCustomer;
TD: TTransactionDesc;

implementation

uses unPrincipal;

{$R *.dfm}

constructor TfrmCustomer.Create(AOwner: TComponent; CUST_NO: Integer);


begin
inherited Create(Owner);
_CUST_NO := CUST_NO;
end;
{...}

 
Vamos adicionar o evento OnCreate no form frmCustomer, neste evento iremos iniciar um transação a partir do
SqlConnection que esta no form principal, somente após a transação iniciada iremos fazer um select filtrando pelo valor chave
da tabela CUSTOMER para que assim apenas um registro seja retornado e travado. Caso o registro já esteja travado por outro
usuário irá ser retornado uma exceção no memento da execução de open, isto por termos alterado a propriedade WaitOnLocks
dos parâmetros de conexão para false, caso contrário como já disse anteriormente, o aplicação ficaria travada aguardando o
momento em que o registro fosse liberado.
Observe que caso haja a exceção faço um tratamento na mensagem de erro, onde se ela retornar 'SQL Server Error: lock
conflict on no wait transaction deadlock update conflicts with concurrent update’, indicará que o registro está em uso, assim
exibo uma mensagem mais amigável, e então executo o método PostMessage(self.handle, WM_CLOSE, 0, 0);  para fechar o form
frmCustomer antes que o mesmo fosse exibido. Veja o código abaixo:
 

procedure TfrmCustomer.FormCreate(Sender: TObject);


const
SQLCustomer: string = 'select * from CUSTOMER where CUST_NO = %d WITH LOCK';
begin
Randomize;
{ Abre uma transação para controlar o processamento todo }
TD.TransactionID := Random(65635);
TD.IsolationLevel := xilREADCOMMITTED;//xilREPEATABLEREAD;
frmPrincipal.sqlConexao.StartTransaction(TD);
cdsCustomer.CommandText := Format(SQLCustomer, [_CUST_NO]);
try
cdsCustomer.Open;
except on E: Exception do
begin
if pos('SQL Server Error: lock conflict on no wait
transaction'#$A'deadlock'#$A'update conflicts with concurrent update',E.Message) > 0
then
ShowMessage('Registro Em Edição, tente mais tarde!');
//Fechar Form antes mesmo que ele seja exibido
PostMessage(self.handle, WM_CLOSE, 0, 0);
end;
end;
end;

 
Para aplicar as modificações deve ser executado após o post o método Applyupdates, assim adicione o evento
OnAfterPost no Clientdataset cdsCustomer, e insira a seguinte instrução:
 

procedure TfrmCustomer.cdsCustomerAfterPost(DataSet: TDataSet);


begin
cdsCustomer.ApplyUpdates(0);
end;

 
O mesmo deve ser feito para o caso de deleção, para isto apenas ligue o evento OnAfterDelete ao método
cdsCustomerAfterPost através do Object Inspector, para fazer o reaproveitamento do código.

theclub.com.br/Restrito/Revistas/200901/lock0901.aspx 5/6
17/08/2020 Artigos

Finalmente iremos adicionar no evento OnClose do form as instruções para confirmar a gravação, ou seja executar o
commit na transação, e no caso de uma falha no processo realizar o rollback da transação, veja o código abaixo:
 

procedure TfrmCustomer.FormClose(Sender: TObject;


var Action: TCloseAction);
begin
try
if cdsCustomer.State in [dsInsert, dsEdit] then
cdsCustomer.Post;
frmPrincipal.sqlConexao.Commit(TD); { Em caso de sucesso, confirma a transação }
except on E: Exception do {Em caso de erro, aborta todo processamento }
if frmPrincipal.sqlConexao.InTransaction then
frmPrincipal.sqlConexao.Rollback(TD);
end;
cdsCustomer.Close;
end;

 
Conclusão
With lock deve ser usado com bastante consciência, pois serão pouquíssimos os casos em que ele é realmente
necessário, o seu uso inadequado pode causar o aumento da mensagem de erro de deadlock, inclusive o nosso exemplo foi
feito apenas com o objetivo de instruir de como utilizar esta cláusula, mas dependendo da aplicação pode não ser viável a sua
utilização para a mesma situação. Considerando o uso apropriado desta cláusula, ele irá facilitar o processo e controle de
travamento de registro.
 
Download do Exemplo:
http://www.theclub.com.br/REVISTA/rev0109/LockRegistro.zip
Download do video aula explicativo:
http://www.theclub.com.br/REVISTA/rev0109/LockRegistroVA.zip
Leia mais sobre a cláusula WITH LOCK no artigo Novidades do FireBird 1.5
http://www.theclub.com.br/REVISTA/NOVI0504.ASPX
 
 
 
 

 
Sobre o Autor

Marcos César Silva, consultor de Sistemas na consultoria de sistemas DataSmart e


Consultor Técnico do The Club, Bacharel em Ciência da Computação, MBA em Gestão
Empresarial, Certificações MCAD (Microsoft Certified Application Developer) e MCSD.NET
(Microsoft Certified Solution Developer .NET)

E-mail: suporte@theclub.com.br

theclub.com.br/Restrito/Revistas/200901/lock0901.aspx 6/6

Você também pode gostar