Estou escrevendo o esquema para um banco de dados simples do banco. Aqui estão as especificações básicas:
- O banco de dados armazenará transações contra um usuário e uma moeda.
- Cada usuário tem um saldo por moeda; portanto, cada saldo é simplesmente a soma de todas as transações com um determinado usuário e moeda.
- Um saldo não pode ser negativo.
O aplicativo do banco se comunicará com seu banco de dados exclusivamente por meio de procedimentos armazenados.
Espero que esse banco de dados aceite centenas de milhares de novas transações por dia, além de equilibrar as consultas em uma ordem de magnitude superior. Para atender saldos muito rapidamente, preciso pré-agregá-los. Ao mesmo tempo, preciso garantir que um saldo nunca contradiga seu histórico de transações.
Minhas opções são:
Tenha uma
balances
tabela separada e siga um destes procedimentos:Aplique transações às tabelas
transactions
ebalances
. Use aTRANSACTION
lógica na minha camada de procedimento armazenado para garantir que os saldos e as transações estejam sempre sincronizados. (Suportado por Jack .)Aplique transações à
transactions
tabela e tenha um gatilho que atualize abalances
tabela para mim com o valor da transação.Aplique transações à
balances
tabela e tenha um gatilho que adicione uma nova entrada natransactions
tabela para mim com o valor da transação.
Eu tenho que confiar em abordagens baseadas em segurança para garantir que nenhuma alteração possa ser feita fora dos procedimentos armazenados. Caso contrário, por exemplo, algum processo poderia inserir diretamente uma transação na
transactions
tabela e, no esquema,1.3
o saldo relevante estaria fora de sincronia.Tenha uma
balances
exibição indexada que agregue as transações adequadamente. Os saldos são garantidos pelo mecanismo de armazenamento para permanecer sincronizados com suas transações, portanto, não preciso depender de abordagens baseadas em segurança para garantir isso. Por outro lado, não posso mais impor saldos não negativos, pois as visualizações - mesmo as visualizações indexadas - não podem terCHECK
restrições. (Suportado por Denny .)Tenha apenas uma
transactions
tabela, mas com uma coluna adicional para armazenar o saldo efetivo logo após a transação ser executada. Assim, o último registro de transação para um usuário e moeda também contém seu saldo atual. (Sugerido abaixo por Andrew ; variante proposta por garik .)
Quando lidei com esse problema, li essas duas discussões e decidi pela opção 2
. Para referência, você pode ver uma implementação básica aqui .
Você projetou ou gerencia um banco de dados como este com um perfil de alta carga? Qual foi a sua solução para esse problema?
Você acha que eu fiz a escolha certa para o design? Há algo que eu deva ter em mente?
Por exemplo, eu sei que alterações de esquema na
transactions
tabela exigirão a reconstrução dabalances
exibição. Mesmo que eu esteja arquivando transações para manter o banco de dados pequeno (por exemplo, movendo-as para outro lugar e substituindo-as por transações de resumo), ter que reconstruir a exibição de dezenas de milhões de transações com cada atualização de esquema provavelmente significará significativamente mais tempo de inatividade por implantação.Se a exibição indexada é o caminho a seguir, como posso garantir que nenhum saldo seja negativo?
Arquivando transações:
Deixe-me detalhar um pouco as transações de arquivamento e as "transações de resumo" que mencionei acima. Primeiro, o arquivamento regular será uma necessidade em um sistema de alta carga como esse. Desejo manter a consistência entre os saldos e o histórico de transações, permitindo que as transações antigas sejam movidas para outro lugar. Para fazer isso, substituirei cada lote de transações arquivadas por um resumo de seus valores por usuário e moeda.
Então, por exemplo, esta lista de transações:
user_id currency_id amount is_summary
------------------------------------------------
3 1 10.60 0
3 1 -55.00 0
3 1 -12.12 0
é arquivado e substituído por este:
user_id currency_id amount is_summary
------------------------------------------------
3 1 -56.52 1
Dessa forma, um saldo com transações arquivadas mantém um histórico de transações completo e consistente.
fonte
Respostas:
Não estou familiarizado com contabilidade, mas resolvi alguns problemas semelhantes em ambientes do tipo inventário. Eu armazeno os totais em execução na mesma linha da transação. Estou usando restrições, para que meus dados nunca estejam errados, mesmo sob alta simultaneidade. Escrevi a seguinte solução em 2009 :
O cálculo dos totais em execução é notoriamente lento, seja você com um cursor ou com uma junção triangular. É muito tentador desnormalizar, armazenar totais em execução em uma coluna, especialmente se você a selecionar com frequência. No entanto, como sempre, quando você desnormaliza, precisa garantir a integridade de seus dados desnormalizados. Felizmente, você pode garantir a integridade dos totais em execução com restrições - desde que todas as suas restrições sejam confiáveis, todos os totais em execução estejam corretos. Dessa forma, você pode facilmente garantir que o saldo atual (totais em execução) nunca seja negativo - a imposição de outros métodos também pode ser muito lenta. O script a seguir demonstra a técnica.
fonte
Não permitir que os clientes tenham um saldo menor que 0 é uma regra de negócios (que mudaria rapidamente, pois as taxas para coisas como saques a descoberto são como os bancos fazem a maior parte de seu dinheiro). Você desejará lidar com isso no processamento do aplicativo quando as linhas forem inseridas no histórico de transações. Especialmente porque você pode acabar tendo alguns clientes com cheque especial, alguns recebendo taxas cobradas e outros não permitindo que valores negativos sejam inseridos.
Até agora, eu gosto de onde você está indo com isso, mas se isso é para um projeto real (não para a escola), é preciso pensar bastante nas regras de negócios, etc. Depois que você criar um sistema bancário e em execução não há muito espaço para redesenho, pois existem leis muito específicas sobre pessoas que têm acesso ao seu dinheiro.
fonte
Uma abordagem ligeiramente diferente (semelhante à sua segunda opção) a considerar é ter apenas a tabela de transações, com uma definição de:
Você também pode querer um ID / pedido de transação, para poder lidar com duas transações com a mesma data e melhorar sua consulta de recuperação.
Para obter o saldo atual, tudo o que você precisa é o último registro.
Métodos para obter o último registro :
Contras:
As transações para o usuário / moeda precisariam ser serializadas para manter um saldo preciso.
Prós:
Edit: Algumas consultas de amostra sobre recuperação do saldo atual e para destacar o golpe (Obrigado @ Jack Douglas)
fonte
SELECT TOP (1) ... ORDER BY TransactionDate DESC
implementação será muito complicada, de forma que o SQL Server não varre constantemente a tabela de transações. Alex Kuznetsov postou uma solução aqui para um problema de design semelhante que complementa perfeitamente esta resposta.Depois de ler essas discussões também, não sei por que você decidiu a solução DRI sobre as outras opções mais sensíveis que você descreve:
Esse tipo de solução possui imensos benefícios práticos se você tiver o luxo de restringir todo o acesso aos dados por meio de sua API transacional. Você perde o benefício muito importante do DRI, que é a integridade garantida pelo banco de dados, mas em qualquer modelo de complexidade suficiente, existem algumas regras de negócios que não podem ser impostas pelo DRI .
Aconselho o uso do DRI, sempre que possível, para impor regras de negócios sem dobrar muito o modelo para tornar isso possível:
Assim que você começar a considerar poluir seu modelo assim, acho que você está se mudando para a área em que o benefício do DRI é superado pelas dificuldades que você está apresentando. Considere, por exemplo, que um bug no seu processo de arquivamento poderia, em teoria, fazer com que sua regra de ouro (que os saldos sempre sejam iguais à soma das transações) se rompa silenciosamente com uma solução DRI .
Aqui está um resumo das vantagens da abordagem transacional como as vejo:
--editar
Para permitir o arquivamento sem adicionar complexidade ou risco, você pode optar por manter as linhas de resumo em uma tabela de resumo separada, gerada continuamente (emprestado de @Andrew e @Garik)
Por exemplo, se os resumos forem mensais:
fonte
Usuario.
A ideia principal é armazenar registros de saldo e transação na mesma tabela. Aconteceu historicamente, pensei. Portanto, nesse caso, podemos obter equilíbrio localizando o último registro de resumo.
Uma variante melhor é o número decrescente de registros de resumo. Podemos ter um registro de saldo no final (e / ou início) do dia. Como você sabe, todo banco precisa
operational day
abrir e depois fechá-lo para fazer algumas operações resumidas para este dia. Permite calcular facilmente os juros usando o registro do saldo diário, por exemplo:Sorte.
fonte
Com base nos seus requisitos, a opção 1 parece a melhor. Embora eu tivesse meu design para permitir apenas inserções na tabela de transações. E tenha o gatilho na tabela de transações, para atualizar a tabela de saldo em tempo real. Você pode usar permissões de banco de dados para controlar o acesso a essas tabelas.
Nesta abordagem, o saldo em tempo real é garantido em sincronia com a tabela de transações. E não importa se procedimentos armazenados ou psql ou jdbc são usados. Você pode verificar seu saldo negativo, se necessário. O desempenho não será um problema. Para obter o equilíbrio em tempo real, é uma consulta única.
O arquivamento não afetará essa abordagem. Você pode ter uma tabela de resumo semanal, mensal e anual também se necessário para itens como relatórios.
fonte
No Oracle, você pode fazer isso usando apenas a tabela de transações com uma Visualização Materializada rápida e atualizável, que faz a agregação para formar o saldo. Você define o gatilho na visão materializada. Se a Visualização Materializada for definida com 'ON COMMIT', ela evita efetivamente a adição / modificação de dados nas tabelas base. O gatilho detecta os [no] dados válidos e gera uma exceção, onde reverte a transação. Um bom exemplo está aqui http://www.sqlsnippets.com/en/topic-12896.html
Eu não sei sqlserver, mas talvez tenha uma opção semelhante?
fonte