Você está na página 1de 10

Guia de boas práticas - Transações

Documento: Guia de boas práticas - Transações

Quando se trabalha num ambiente multiusuário, se faz


necessário um controle de concorrência das atualizações
realizadas no banco de dados. Controle de concorrência é
um método usado para garantir que as transações sejam
executadas de forma segura e que sigam as regras de
Atomicidade, Consistência, Isolamento e Durabilidade. Uma
transação é uma unidade que preserva consistência.É
necessário, portanto, que qualquer escalonamento
produzido ao se processar um conjunto de transações
concorrentemente seja computacionalmente equivalente a
um escalonamento executando essas transações
serialmente em alguma ordem. Diz-se que um sistema que
garante esta propriedade assegura a seriabilidade. A
serialização das operações reduz o desempenho da
aplicação, principalmente quando realizada em tabelas de
alta concorrência.
A linha Microsiga Protheus dispõe de um padrão para as
operações de concorrência, que visam reduzir as
ocorrênicas da serialização da aplicação, estas operações
podem ser dividas em:
· Leitura
· Bloqueio de Interface
· Bloqueio de processamento
· Bloqueio de transação

Leitura
A linha Microsiga Protheus adota o nível de isolamento READ UNCOMMITTED. Este nível de isolamento permite leituras sujas, onde a transação

corrente ou as demais podem visualizar os dados das transações em aberto.

O nível de isolamento adotado garante que as leituras realizadas no banco de dados pela aplicação não realizem bloqueio de registros e por

conseqüência não serializem a aplicação, mantendo a previsibilidade do desempenho. Porém alguns cuidados devem ser observados para

garantir a integridade dos dados.

Quando se adota leituras sujas deve-se prevenir que em atualizações, os dados sejam bloqueados antes de serem atualizados ou lidos com

este fim. A transação T2 tem uma linha alterada, mas ainda não finalizada. A transação T1 lê os dados atualizados e os atualiza com base nos

dados lidos. A transação T1 fecha a transação e a transação T2 realiza um Roll Back dos dados. A partir deste momento os dados não estão

mais íntegros.

Para evitar esta situação, o desenvolvedor deve indicar a


aplicação o momento do bloqueio, isto é feito utilizando a
função RecLock, conforme demonstrado abaixo:
Begin Transaction

RecLock(“SB2”)

nQtd := SB2->B2_QATU

If nQtd + nSoma <= 0

SB2->B2_QATU := 0

Else

SB2->B2_QATU := nQtd - nSoma

EndIf

MsUnLock()

End Transaction

A função RecLock informa para o Framework da aplicação


realizar o bloqueio do registro posicionado. O Framework
realiza o bloqueio e atualiza a leitura do registro corrente da
tabela. As demais transações do sistema continuam lendo
o registro bloqueado, porém se tentarem bloquear o
registro deverão aguardar a transação anterior liberá-lo, o
que somente pode ser realizado ao termino da transação.
Outra prevenção a ser considerada nas leituras sujas são
as atualizações parciais, decorrentes do relacionamento de
dependência direta das tabelas. Para um formulário do tipo
Master/Detail (cabeçalho e Item), a transação de inserção
deste formulário permite que o registro da tabela Master
exista sem os registros ou com uma quantidade parcial dos
registros da tabela Detail, haja vista que é a única forma de
garantir a dependência da chave estrangeira. Se uma rotina
de atualização realizar o bloqueio apenas nos registros
Detail não há como garantir que todos os registros sejam
considerados. Portanto é necessário bloquear o registro
Master, antes do inicio do bloqueio dos registros Detail em
qualquer atualização dos registros Detail. Ao bloquear o
registro Master, garante-se que nenhum registro da tabela
Detail, vinculado ao Master, esteja em atualização ou
inserção. É fato que dependendo da rotina desenvolvida, é
obrigatório iniciar a leitura pela tabela Detail e não pela
Master. Nestas situações deve-se garantir que, antes de
qualquer atualização do registro Detail, o registro Master
seja bloqueado antes do registro Detail.
No exemplo são mencionadas apenas duas tabelas, porém
independentemente da quantidade de tabelas envolvidas no
formulário, o registro Master deve ser bloqueado.
Por último, existe a prevenção do momento do COMMIT. A
arquitetura do Server Application da linha Microsiga
Protheus faz com que o momento do COMMIT seja
automático, conforme a lógica do programa, assim como
pode utilizar mais de um canal de comunicação com o
SGBD para leitura. Se o Server Application utiliza mais de
um canal para leitura, é possível inferir que os demais
canais de leitura somente “visualizarão” os registros, cujos
dados foram comitados. Para prevenir qualquer tipo
problema na leitura através de querys, é necessário forçar o
COMMIT dos dados, conforme demonstrado abaixo.
SPED050->(dbCommit())

BeginSql Alias cAlias

SELECT R_E_C_N_O_

FROM SPED050

WHERE

ID_ENT = %Exp:cIdEnt% AND

(STATUS = 1 OR

STATUS = 2 OR

STATUSDPEC = 1) AND

STATUSDPEC <> 5 AND

%NOTDEL%

EndSql

O nível de isolamento READ UNCOMMITTED é o mais


aconselhável para aplicação do tipo de aplicação OLTP (On
Line Transaction Process), porém cabe ao desenvolvedor o
controle do formato de leitura e o bloqueio do registro para
atualização.

Bloqueio de interface
Num ambiente multiusuário a probabilidade de dois
usuários alterarem o mesmo registro de um cadastro,
simultaneamente, é muito alta. Por esta razão faz-se
necessário impedir o acesso de um deles.
O Framework da linha Microsiga Protheus possui a função
Softlock para esta finalidade. Esta função deve ser utilizada
para bloquear um determinado registro, em atualização,
durante as operações de interface, conforme pode ser
observado abaixo:
If SoftLock(cAlias)

RegToMemory(cAlias,.F.,lVirtual)

DEFINE MSDIALOG ___oDlg OF oFather:oWnd FROM 0, 0 TO 0, 0 PIXEL STYLE nOR(

WS_VISIBLE, WS_POPUP )

aPosEnch := {,,,}

oEnc01:= MsMGet():New( cAlias, nReg, nOpc, ,"CRA", oemtoansi(STR0004), aAcho, aPosEnch

,aCpos,,,,cTudoOk,___oDlg ,,lVirtual,.F.,,,,,,,,cTela) //"Quanto …s altera‡”es?"

oEnc01:oBox:align := CONTROL_ALIGN_ALLCLIENT

bEndDlg := {|lOk| If(lOk:=___oDlg:End(),nOpcA:=1,nOpcA:=3), lOk}

___oDlg:nWidth := aDim[4]-aDim[2]

ACTIVATE MSDIALOG ___oDlg ON INIT ( FaMyBar(___oDlg,{||

If(Obrigatorio(aGets,aTela).And.Eval(bOk).And.Eval(bOk2,nOpc),Eval(bEndDlg),(nOpcA:=3,.f.))},{||

nOpcA := 3,___oDlg:End()},aButtons), ___oDlg:Move(aDim[1],aDim[2],aDim[4]-aDim[2],

aDim[3]-aDim[1]) )

(cAlias)->(MsGoTo(nReg))

If nOpcA == 1

Begin Transaction

RecLock(cAlias,.F.)

For nX := 1 TO FCount()

FieldPut(nX,M->&(EVAL(bCampo,nX)))

Next nX

If Type("aMemos") == "A"

For nX := 1 to Len(aMemos)

cVar := aMemos[nX][2]

cVar1:= aMemos[nX][1]

MSMM(&cVar1,TamSx3(aMemos[nX][2])[1],,&cVar,1,

,,cAlias,aMemos[nX][1],cAliasMemo)

Next nX

EndIf
EndIf

A função SoftLock garante que a interface somente seja


montada para o primeiro usuário que fez a tentativa de
atualização ou exclusão. Enquanto a transação não for
abortada ou concluída, o registro permanecerá bloqueado
para os demais. Note que apesar da função SoftLock realiza
r um bloqueio, o uso da função RecLock continua a ser
necessário na transação.

Bloqueio de processamento
Em rotinas de processamento em ambientes multiusuários
é comum dois usuários solicitarem o mesmo
processamento simultaneamente.
Exemplo: Usuário A e B solicitam a liberação de todos os
pedidos em aberto. Nesta situação há duas alternativas:
· Bloquear a rotina para uso Mono-usuário
· Tratar a concorrência
O bloqueio da rotina para uso Monousuário deve ser
evitado, porém se necessário deve ser feito através do uso
de um semáforo. A função LockByNam e é a mais
recomendada para esta finalidade. Porém deve-se atentar o
isolamento necessário. Caso a rotina seja multiempresa,
deve-se considerar a empresa, caso for multifilial deve-se
considerar a filial na formação da chave de isolamento.
Exemplo: Para que rotina MATA330 esteja isolada de todas
as empresas e filiais, deve-se utilizar o Grupo de Empresas
na formação da chave de isolamento
LockByName(“MATA330_”+cEmpAnt). Se a necessidade for
apenas do isolamento da filial, deve-se utilizar a filial na
formação da chave de isolamento
LockByName(“MATA330_”+cFilAnt)
Tratar a concorrência em rotinas de processamento é uma
tarefa simples, que traz um enorme beneficio para o
desempenho da aplicação. Quando dois usuários executam
a mesma rotina simultaneamente é muito provável que um
dos usuários bloqueie o registro principal e os demais
fiquem aguardando a liberação do bloqueio. Após a
liberação do bloqueio um dos usuários conseguirá fazer o
bloqueio, porém não haverá processamento a ser feito e o
próximo registro já estará bloqueado. Como é possível
notar, não há beneficio algum para o sistema, mas há o
prejuízo de consumo de hardware do Application Server.

Para que o sistema tenha beneficio neste tipo de situação é


imprescindível o uso da função SimpleLock. Esta função
avalia se o registro pode ser bloqueado e caso não consiga,
a rotina passará para o próximo registro e assim
sucessivamente até o termino do processamento. A grande
vantagem de seu uso é que, quanto mais chamadas o
cliente fizer da mesma rotina, mais rápido será o
processamento da rotina.
Registros da Tabela Momento Chamada 1 Chamada 2 Chamada 3
10 1 Bloqueia 10 Bloqueia 11 Bloqueia 12
11 2 Libera 10 Libera 11 Libera 12
12 3 Encerra Encerra Encerra

Note que este tipo de solução pode ser utilizado em vários


tipos de rotina de processamento, desde que haja um
ponteiro para execução.
Bloqueio de transação

As transações tendem a serializar o sistema e deixá-lo mais


lento em ambientes multiusuário. Para mitigar a
serialização são necessários alguns cuidados.
O primeiro cuidado a se tomar, é evitar um DeadLock. Um
sistema está em estado de DeadLock quando existe uma
operação (A) fazendo um bloqueio em um registro (R1) e
tentando bloquear outro registro (R2). Neste mesmo
momento existe outra operação (B) bloqueando o registro
(R2) e tentando bloquear o registro (R1). Nesta situação não
existe como o banco resolver as solicitações, então ele
elege, aleatoriamente, uma das conexões e a encerra
provocando um erro para o usuário.

Operação A
R1 Bloqueado R2
R2 Aguardando Bloqueio R1

Existem duas maneira de resolver um DeadLock. A primeira


é garantir que a transação não bloqueie mais de um
registro da mesma tabela. Isto pode ser feito reduzindo o
tamanho da transação, de modo a garantir e manter a
integridade do sistema.
Exemplo: Ao gravar um formulário do tipo Master/Detail
pode-se fazer uma transação para o Master e primeiro
registro Detail e outra para os demais itens.
For nX := 1 To Len(aItens)

BEGIN TRANSACTION
If nX == 1

RecLock(“SC5”,.T.)

...

EndIf

RecLock(“SC6”,.T.)

MaAvalSc6()

CLOSETRANSACTION LOCKIN "SC5"

Next nX

BEGIN TRANSACTION

RecLock(“SC5”)

MaAvalSc5()

END TRANSACTION

Note que durante todo o processo, a tabela SC5


está bloqueada e somente é liberada na última transação.
Isto é garantido pelo comando CLOSETRANSACTION LOCK
IN, que atualiza a transação, mas mantém o bloqueio do
registro para não gerar problemas em outros
processamentos concorrentes, conforme informado
anteriormente.
Outra maneira de resolver, é utilizando a função MultLock.
Esta função garante o bloqueio de todos os registros de
uma mesma tabela. A sua desvantagem é o aumento da
serialização do produto. Por este motivo, deve-se utilizá-la
com muito cuidado e ter em mente que sempre há uma
alternativa para evitar-se o seu uso.
If MultLock("SB2",aMults,1)

BEGIN TRANSACTION


END TRANSACTION

EndIf

A escolha de um dos métodos vai depender do tipo da


transação.
Transações de formulário devem, obrigatoriamente, seguir
o primeiro exemplo. As transações são mais rápidas e o
risco é a atualização parcial do formulário digitado, porém
ele estará íntegro. Com certeza o usuário final prefere
perder alguns dados a tudo.
Porém existirão casos em que a atualização parcial não
fornece uma transação íntegra. Para estes casos, deve-se
utilizar “MultLock”. Um bom exemplo são as notas fiscais,
em que em caso de queda, o usuário tende a cancelar a
nota e refazê-la se houver falta de itens, ou seja, o modelo
anterior não traz benefícios ao usuário.