Página 1 de 1
Integridade e segurança na gravação de dados no DBF
Enviado: 30 Set 2009 07:28
por SandroBelarmino
Estou tendo alguns problemas em uma empresa na gravação do histórico da movimentação do produto, e estou em dúvida quanto a rotina que estou usando nessa gravação, se é a melhor maneira ou se tem alguma outro jeito mais seguro e garantido.
A empresa tem +/- 30 micros na rede e essa rotina geralmente é usada por uns 5 micros ao mesmo tempo, e em alguns casos eu simplesmente dou um append para inserir o registro no dbf e em alguns casos vou até um registro já existente e faço um update no mesmo.
A rotina que estou usando é a seguinte:
Para inserir um novo registro:
Código: Selecionar todos
Sele prod130
do while .t.
if adi_reg()
repla codigo with wCodEst,;
data with date(),;
local with 'FABRICA',;
ref with prod040->ref,;
quant with wQuant,;
mov with 'S',;
motivo with 'EMBARQUE',;
datasis with date(),;
horasis with time(),;
usuario with gusuario,;
pedido with wCodPedido
exit
Endif
enddo
dbcommit()
Para atualizar um registro já existente:
Código: Selecionar todos
Sele prod130
Seek wIdEstoque
do while .t.
if net_reg()
repla quant with quant+wquant,;
horasis with time()
unlock
exit
endif
enddo
dbcommit()
Usando o replace e dando o dbcommit() é a melhor maneira? Pois já vi em alguns exemplos usarem da seguinte forma por exemplo:
Código: Selecionar todos
prod130->quant := prod130->quant+wquant
prod130->horasis := time()
Qual a maneira mais indicada? E quanto a concorrencia da rede, se por acaso mais de 1 micro tentar fazer a inclusao de um registro novo ao mesmo tempo? (append)
Grato
Sandro
Re: Integridade e segurança na gravação de dados no DBF
Enviado: 30 Set 2009 08:20
por anacatacombs
Bom Dia Sandro.
Eu uso quase do mesmo jeito que vc usa..
Porém, antes de usar o UNLOCK, eu costumo utilizar o DBCOMMIT().. não sei até onde pode fazer diferença..
Código: Selecionar todos
Sele prod130
do while .t.
if adi_reg()
repla codigo with wCodEst,;
data with date(),;
local with 'FABRICA',;
ref with prod040->ref,;
quant with wQuant,;
mov with 'S',;
motivo with 'EMBARQUE',;
datasis with date(),;
horasis with time(),;
usuario with gusuario,;
pedido with wCodPedido
dbcommit() // aqui
unlock // e aqui
exit
ELSE
// mais um aviso de que a operação não foi concluida.
Endif
enddo
Não sei como você esta fazendo o controle da chave primária.., mas imagino que em rede, esse é um dos problemas que vc poderá ter, principalmente se for código sequencial ...
Re: Integridade e segurança na gravação de dados no DBF
Enviado: 30 Set 2009 09:06
por SandroBelarmino
Então, eu tenho dúvida sobre o uso do dbcommit() antes ou depois do unlock. Nao sei se faz alguma diferença. Já procurei no NG mas nao entendi direito o que diz lá.
Re: Integridade e segurança na gravação de dados no DBF
Enviado: 30 Set 2009 09:27
por gvc
{O DBUnlock força a gravação dos dados que estão no buffer da sua máquina no banco de dados.} Errado!!!
o correto é:
O DBCommit força a gravação dos dados que estão no buffer da sua máquina no banco de dados.
Então vc que lembrar o seguinte:
Vc DEVE forçar a gravação antes de desbloquear o registro.
Código: Selecionar todos
if adi_reg()
prod130->quant += wquant
prod130->horasis := time()
dbcommit()
dbunlock()
end
Se vc liberar o registro antes de atualizar os dados, corre o risco de outro usuário ler/gravar dados errados.
Isso ocorre muito quando os dados são atualizados constantemente. Saldo de peças, por exemplo.
O que eu não entendi é pq vc coloca a gravação dentor do While.
Re: Integridade e segurança na gravação de dados no DBF
Enviado: 30 Set 2009 09:59
por SandroBelarmino
gvc escreveu:O que eu não entendi é pq vc coloca a gravação dentor do While.
Pra ter certeza de que vai ser gravado, pois só vai sair do laço quando a funcao adi_reg() retornar .t., assim faz a gravação e sai do do while.
Agora, sobre usar o replace codigo with wcodigo ou usar prod130->codigo := codigo, tem alguma diferença? algum deles é mais garantido?~
Grato,
Sandro.
Re: Integridade e segurança na gravação de dados no DBF
Enviado: 30 Set 2009 10:07
por billy1943
Depende muito da forma de atualizar o estoque (presumo que seja um arquivo de movimentação com entradas e saídas). Se for somente para gravar o movimento gerado por um terminal, o melhor seria:
1 - acesse o arquivo de movimento
2 - adicione o registro testando a rede primeiro
3 - DCOMMIT()
4 - DBUNLOCK()
Agora, atualizar um estoque obtendo um saldo com um número grande de terminais incluindo, alterando registros de movimento, sempre haverá um grande risco de pegar "saldo furado", dependendo da falta de sincronia que pode haver.
O melhor é uma rotina de atualização feita por cada estação, quando terminar de fazer as inclusões ou alterações do movimento. Aí o sistema ordenará todo o movimento (pode ser somente do dia, não mexendo em datas anteriores), por transação (entradas primeiro e depois as saídas), por hora, minuto e segundo, e regrava o saldo correto no arquivo de produtos, assinala com alguma flag o registro já incluído para na próxima transação não se perder mais tempo com ele.
Eu uso em meus sistemas de estoque um arquivo temporário para cada terminal, de modo que quando a rotina de inclusão de movimento for fechada, aí sim o movimento de entrada e saída é gravado em arquivos de lotes distintos.
O arquivo de produtos que contém todos os campos de acumulação (saldo atual, valores de entrada e saída, etc) será processado pelos arquivos de lotes, preservando-se sempre um saldo inicial de período (final do mês anterior, por exemplo), e esse processamento poderá ser feito de modo automático no início do dia, pelo primeiro terminal que for ligado, sobrepujando todas as atualizações feitas no decorrer do dia anterior.
Re: Integridade e segurança na gravação de dados no DBF
Enviado: 30 Set 2009 10:27
por jelias
Amiguinho,
Talvez esteja falando de um procedimento que você já esteja utilizando na sua função adi_reg() todavia é importante testar se o DBAPPEND() obteve êxito.
Eu sempre utilizo da seguinte forma.
sele AL_FORNE
ADDREDE()
repl ...
repl ...
repl ...
DBCOMMIT()
DBUNLOCK()
E para salvar as alterações:
RLOCK()
repl
repl
repl
DBCOMMIT()
DBUNLOCK()
******************
Function ADDREDE()
******************
do While .T.
DbAppend() // DBAPPEND() cria um registro novo e Bloqueia o mesmo, até hj funcionou legal, em mono e multiusuário...
If !NetErr()
Return(.T.)
Else
MENSAGEM("Aguarde Inserindo Novo Registro...")
Inkey(1)
Loop
End
End
Return(Nil)
A sugestão para o correto uso é conforme está descrito acima, a trinca RLOCK() - DBCOMMIT() - DBUNLOCK(). Todavia, já vi alguns programadores realizarem esta seguência de maneira diferente, RLOCK() - DBUNLOCK() - DBCOMMIT(). Eu utilizo a primeira seguência e nunca tive problemas, e outros amigos narram que nunca tiveram problemas com a segunda seguência também.
Tenho uma rotina com vários usuários cadastrando ao mesmo tempo em terminais diferentes e funciona sem problemas.
Sds,
Júlio.
Re: Integridade e segurança na gravação de dados no DBF
Enviado: 30 Set 2009 13:21
por alaminojunior
gvc escreveu:O DBUnlock força a gravação dos dados que estão no buffer da sua máquina no banco de dados.
Você quis dizer DbCommit().
billy1943 escreveu:Agora, atualizar um estoque obtendo um saldo com um número grande de terminais incluindo, alterando registros de movimento, sempre haverá um grande risco de pegar "saldo furado", dependendo da falta de sincronia que pode haverEu uso em meus sistemas de estoque um arquivo temporário para cada terminal, de modo que quando a rotina de inclusão de movimento for fechada, aí sim o movimento de entrada e saída é gravado em arquivos de lotes distintos.
Posso estar enganado (pois você diz que funciona), mas isso não seria um trabalho a mais ? e perigoso ? Digo "trabalho a mais" pois a tarefa vai ser feita do mesmo jeito, apenas em momento diferente se é que eu entendí. "Perigoso" pois o mesmo terminal pode se deparar com uma tabela que ainda não foi atualizada.
Penso que as rotinas devem ser acessadas seguindo uma hierarquia., por exemplo: no caso de entrada de mercadorias, um terminal não pode ter acesso à mesma nota fiscal que já esteja sendo lançada por outro; no caso de vendas é mais simples, pois como todos precisam ter os dados atualizados em tempo real, é necessário "apenas" uma boa política de travamentos e gravações; no caso de manutenção em tabela de clientes, o mesmo caso da entrada de mercadorias, ou seja, um terminal apenas teria acesso à escrita de dados para determinado cliente.
Re: Integridade e segurança na gravação de dados no DBF
Enviado: 01 Out 2009 00:54
por alxsts
Olá!
Como todos sabem, desde o lançamento do Clipper 5.0 ou 5.01 (não me lembro), a instalação padrão coloca na pasta C:\Clipper5\Source\Sample alguns exemplos de código. Dentre eles, existe um com o nome de Locks.Prg, que exemplifica alguns aspectos da dúvida postada inicialmente.
Código: Selecionar todos
/***
*
* Locks.prg
*
* Sample networking functions to supplant the use of USE,
* FLOCK(), RLOCK() and APPEND BLANK by adding additional
* functionality
*
* Copyright (c) 1993, Computer Associates International Inc.
* All rights reserved.
*
* NOTE: Compile with /a /m /n /w options
*
*/
#include "Common.ch"
#define NET_WAIT 0.5 // Seconds to wait between between retries
#define NET_SECS 2 // Number of seconds to continue retry
/***
*
* AddRec( [<nWaitSeconds>] ) --> lSuccess
*
* Attempt to APPEND BLANK with optional retry
*
* Parameter:
* nWaitSeconds - Optional time in seconds to retry operation, defaults
* to NET_SECS
*
* Returns:
* True (.T.) if successful, false (.F.) if not
*
*/
FUNCTION AddRec( nWaitSeconds )
LOCAL lForever // Retry forever?
DEFAULT nWaitSeconds TO NET_SECS
APPEND BLANK
IF !NETERR()
RETURN ( .T. ) // NOTE
ENDIF
lForever := ( nWaitSeconds == 0 )
// Keep trying as long as our time's not up
DO WHILE ( lForever .OR. ( nWaitSeconds > 0 ) )
APPEND BLANK
IF !NETERR()
RETURN ( .T. ) // NOTE
ENDIF
INKEY( NET_WAIT ) // Wait NET_WAIT seconds (defined above)
nWaitSeconds -= NET_WAIT
ENDDO
RETURN ( .F. ) // Not locked
/***
*
* FilLock( [<nWaitSeconds>] ) --> lSuccess
*
* Attempt to FLOCK() with optional retry
*
* Parameter:
* nWaitSeconds - Optional time in seconds to retry operation, defaults
* to NET_SECS
*
* Returns:
* True if successful, false if not
*
*/
FUNCTION FilLock( nSeconds )
LOCAL lForever // Retry forever?
DEFAULT nSeconds TO NET_SECS
IF FLOCK()
RETURN ( .T. ) // NOTE
ENDIF
lForever := ( nSeconds == 0 )
// Keep trying until our time's up
DO WHILE ( lForever .OR. ( nSeconds > 0 ) )
INKEY( NET_WAIT ) // Wait NET_WAIT seconds
nSeconds -= NET_WAIT
IF FLOCK()
RETURN ( .T. ) // NOTE
ENDIF
ENDDO
RETURN ( .F. ) // Not locked
/***
*
* NetUse( <cDatabase>, <lOpenMode>, [<nWaitSeconds>] ) --> lSuccess
*
* Attempt to USE a database file with optional retry
*
* Parameters:
* cDatabase - Database file to open
* lOpenMode - Sharing mode: True indicates EXCLUSIVE, false
* indicates SHARED
* nWaitSeconds - Optional time in seconds to retry operation, defaults
* to NET_SECS
*
* Returns:
* True if successfull, false if not
*
*/
FUNCTION NetUse( cDatabase, lOpenMode, nSeconds )
LOCAL lForever // Retry forever?
DEFAULT nSeconds TO NET_SECS
lForever := ( nSeconds == 0 )
// Keep trying as long as our time's not up
DO WHILE ( lForever .OR. ( nSeconds > 0 ) )
// lOpenMode determines the mode files are opened in
IF lOpenMode
USE ( cDatabase ) EXCLUSIVE
ELSE
USE ( cDatabase ) SHARED
ENDIF
IF !NETERR()
RETURN ( .T. ) // NOTE
ENDIF
INKEY( NET_WAIT ) // Wait
nSeconds -= NET_WAIT
ENDDO
RETURN ( .F. ) // USE fails
/***
*
* RecLock( [<nWaitSeconds>] ) --> lSuccess
*
* Attempt to RLOCK() with optional retry
*
* Parameter:
* nWaitSeconds - Optional time in seconds to retry operation, defaults
* to NET_SECS
*
* Returns:
* True if successful, false if not
*
*/
FUNCTION RecLock( nSeconds )
LOCAL lForever // Retry forever?
DEFAULT nSeconds TO NET_SECS
IF RLOCK()
RETURN ( .T. ) // NOTE
ENDIF
lForever := ( nSeconds == 0 )
DO WHILE ( lForever .OR. ( nSeconds > 0 ) )
IF RLOCK()
RETURN ( .T. ) // NOTE
ENDIF
INKEY( NET_WAIT ) // Wait 1/2 second
nSeconds -= NET_WAIT
ENDDO
RETURN ( .F. ) // Not locked
Quanto aos passos para gravação, prefiro a seqüência RLOCK(), campo := <valor>, DBCOMMIT() e DBUNLOCK().
O comando REPLACE é originário do dBase e foi mantido no Clipper para compatibilidade. O lançamento do Clipper 5 permitiu que usássemos os então novos operadores
in line, (:=, ++, --, +=, -=, entre outros). Isso, combinado com os
code blocks, deu um poder maior à linguagem, sem contar a flexibilidade. Você não pode usar um
dentro de um
code block mas, certamente poderá usar
REPLACE ou o operador de atribuição em linha (:=) são transformados na mesma operação e esta não afeta a segurança na gravação de informações.
Re: Integridade e segurança na gravação de dados no DBF
Enviado: 01 Out 2009 12:06
por gvc
SandroBelarmino escreveu:
Pra ter certeza de que vai ser gravado, pois só vai sair do laço quando a funcao adi_reg() retornar .t., assim faz a gravação e sai do do while.
Adi_reg, pelo que entendi só abre o registro em branco.
Os dados são gravados realmente pelo quando vc usa o replace ou <area>-><campo> := <valor>.
É que na função que a maioria usa, a verificação se conseguiu criar o registro em branco já faz o loop. Na verdade, a que eu uso recebe um parâmetro para dizer quantas vezes vc quer que tente criar o registro. A função tenta criar, se teve problemas espera 1 segundo e tenta novamente quantas vezes for informado. Se vc passar um parâmetro 0 (zero), a função fica tentando até conseguir ou o sistema ser abortado.
Acho que assim é mais prático. Menos coisa para se preocupar e codificar.
A função retorna se conseguiu ou não criar o novo registro.
Código: Selecionar todos
if adi_reg(0) // Tenta até conseguir criar o registro em branco ou até o sistema ser interrompido.
prod130->quant += wquant
prod130->horasis := time()
dbcommit()
dbunlock()
end
Eu tive problemas por inverter o DBCommit e o DBUnlock.
Não é constante. Então é dificil de determinar.
O que vc pode imaginar é o Murph trabalhando. Naquele micro segundo em que vc liberou o registro, outro usuário lê o valor errado, e vc grava a informação correta. Para o outro usuário, a informação que ele leu esta valendo. Qdo ele vai gravar os dados, estes serão gravados com o erro.
Mas isso é muito raro? Sim. Por isso que é um erro intermitente e dificil de determinar.
Fica só a dica. Eu ví isso acontecer e procuro evitar correr novos riscos.
Re: Integridade e segurança na gravação de dados no DBF
Enviado: 01 Out 2009 13:38
por SandroBelarmino
Valeu pelas dicas e disposição de todos em ajudar.
Vou fazer as mudanças no sistema, principalmente inverter os dbcommit e dbunlock.
Abraços.
Sandro.
Re: Integridade e segurança na gravação de dados no DBF
Enviado: 01 Out 2009 19:12
por asimoes
Olá a todos,
Uma técnica que eu utilizo e sempre funciona é gerar temporários com a mesma estrutura das bases reais, principalmente em rede, cada usuário tem uma imagem de dbfs temporários. Uma única estação faz a importação deste registros, pode-se colocar a aplicação que vai importar no próprio servidor com agendamento para fazer de n em n minutos.
Com isso meus problemas de indices e dbf corrompidos foram 100% sanados. Ganha-se também performance na aplicação.