Estoy escribiendo el esquema para una base de datos bancaria simple. Aquí están las especificaciones básicas:
- La base de datos almacenará transacciones contra un usuario y moneda.
- Cada usuario tiene un saldo por moneda, por lo que cada saldo es simplemente la suma de todas las transacciones contra un usuario y moneda determinados.
- Un saldo no puede ser negativo.
La aplicación bancaria se comunicará con su base de datos exclusivamente a través de procedimientos almacenados.
Espero que esta base de datos acepte cientos de miles de transacciones nuevas por día, así como consultas de saldo en un orden de magnitud más alto. Para servir saldos muy rápidamente, necesito agregarlos previamente. Al mismo tiempo, necesito garantizar que un saldo nunca contradiga su historial de transacciones.
Mis opciones son:
Tenga una
balances
mesa separada y realice una de las siguientes acciones:Aplicar transacciones a las tablas
transactions
ybalances
. Usar laTRANSACTION
lógica en mi capa de procedimiento almacenado para garantizar que los saldos y las transacciones estén siempre sincronizados. (Apoyado por Jack )Aplique transacciones a la
transactions
tabla y tenga un activador que actualice labalances
tabla para mí con el monto de la transacción.Aplique transacciones a la
balances
tabla y tenga un activador que agregue una nueva entrada en latransactions
tabla para mí con el monto de la transacción.
Tengo que confiar en enfoques basados en la seguridad para asegurarme de que no se puedan realizar cambios fuera de los procedimientos almacenados. De lo contrario, por ejemplo, algún proceso podría insertar directamente una transacción en la
transactions
tabla y, según el esquema,1.3
el saldo relevante no estaría sincronizado.Tener una
balances
vista indizada que agregue las transacciones de manera adecuada. El motor de almacenamiento garantiza que los saldos estén sincronizados con sus transacciones, por lo que no necesito confiar en enfoques basados en la seguridad para garantizar esto. Por otro lado, ya no puedo exigir que los saldos ya no sean negativos, ya que las vistas, incluso las vistas indexadas, no pueden tenerCHECK
restricciones. (Apoyado por Denny .)Tenga solo una
transactions
tabla pero con una columna adicional para almacenar el saldo efectivo inmediatamente después de la transacción ejecutada. Por lo tanto, el último registro de transacción para un usuario y moneda también contiene su saldo actual. (Sugerido a continuación por Andrew ; variante propuesta por garik .)
Cuando abordé este problema por primera vez, leí estas dos discusiones y decidí la opción 2
. Como referencia, puede ver una implementación básica aquí .
¿Ha diseñado o administrado una base de datos como esta con un perfil de alta carga? ¿Cuál fue su solución a este problema?
¿Crees que he tomado la decisión de diseño correcta? ¿Hay algo que deba tener en cuenta?
Por ejemplo, sé que los cambios de esquema en la
transactions
tabla requerirán que reconstruya labalances
vista. Incluso si estoy archivando transacciones para mantener la base de datos pequeña (por ejemplo, moviéndolas a otro lugar y reemplazándolas con transacciones sumarias), tener que reconstruir la vista de decenas de millones de transacciones con cada actualización de esquema probablemente significará significativamente más tiempo de inactividad por implementación.Si la vista indexada es el camino a seguir, ¿cómo puedo garantizar que ningún saldo sea negativo?
Archivado de transacciones:
Permítanme elaborar un poco sobre el archivo de transacciones y las "transacciones resumidas" que mencioné anteriormente. Primero, el archivado regular será una necesidad en un sistema de alta carga como este. Quiero mantener la coherencia entre los saldos y sus historiales de transacciones al tiempo que permitir que las transacciones antiguas se trasladen a otro lugar. Para hacer esto, reemplazaré cada lote de transacciones archivadas con un resumen de sus montos por usuario y moneda.
Entonces, por ejemplo, esta lista de transacciones:
user_id currency_id amount is_summary
------------------------------------------------
3 1 10.60 0
3 1 -55.00 0
3 1 -12.12 0
se archiva y se reemplaza con esto:
user_id currency_id amount is_summary
------------------------------------------------
3 1 -56.52 1
De esta manera, un saldo con transacciones archivadas mantiene un historial de transacciones completo y consistente.
fuente
Respuestas:
No estoy familiarizado con la contabilidad, pero resolví algunos problemas similares en entornos de tipo inventario. Almacene totales acumulados en la misma fila con la transacción. Estoy usando restricciones, por lo que mis datos nunca están equivocados, incluso bajo alta concurrencia. En 2009 escribí la siguiente solución :
El cálculo de los totales acumulados es notoriamente lento, ya sea que lo haga con un cursor o con una unión triangular. Es muy tentador desnormalizar, almacenar totales acumulados en una columna, especialmente si lo selecciona con frecuencia. Sin embargo, como es habitual cuando se desnormaliza, debe garantizar la integridad de sus datos desnormalizados. Afortunadamente, puede garantizar la integridad de los totales acumulados con restricciones: siempre y cuando todas sus restricciones sean confiables, todos sus totales correctos son correctos. También de esta manera puede asegurarse fácilmente de que el saldo actual (totales acumulados) nunca sea negativo: la aplicación por otros métodos también puede ser muy lenta. El siguiente guión demuestra la técnica.
fuente
No permitir que los clientes tengan un saldo inferior a 0 es una regla comercial (que cambiaría rápidamente ya que las comisiones por cosas como el giro bancario son la forma en que los bancos obtienen la mayor parte de su dinero). Querrá manejar esto en el procesamiento de la aplicación cuando se inserten filas en el historial de transacciones. Especialmente porque puede terminar con algunos clientes que tienen protección contra sobregiros y algunos cobran tarifas y algunos no permiten ingresar cantidades negativas.
Hasta ahora me gusta a dónde vas con esto, pero si esto es para un proyecto real (no para la escuela), es necesario pensar mucho en las reglas de negocios, etc. Una vez que tienes un sistema bancario en funcionamiento y en funcionamiento no hay mucho espacio para el rediseño, ya que hay leyes muy específicas sobre las personas que tienen acceso a su dinero.
fuente
Un enfoque ligeramente diferente (similar a su segunda opción) a considerar es tener solo la tabla de transacciones, con una definición de:
También es posible que desee un ID / pedido de transacción, para que pueda manejar dos transacciones con la misma fecha y mejorar su consulta de recuperación.
Para obtener el saldo actual, todo lo que necesita es el último registro.
Métodos para obtener el último registro :
Contras:
Las transacciones para el Usuario / Moneda deberían ser serializadas para mantener un saldo exacto.
Pros:
Editar: Algunas consultas de muestra sobre la recuperación del saldo actual y resaltar estafa (Gracias @Jack Douglas)
fuente
SELECT TOP (1) ... ORDER BY TransactionDate DESC
va a ser muy difícil de poner en práctica de tal manera que SQL Server no analiza constantemente la tabla de transacciones. Alex Kuznetsov publicó una solución aquí para un problema de diseño similar que complementa perfectamente esta respuesta.Después de leer esas discusiones también, no estoy seguro de por qué decidió la solución DRI sobre la más sensata de las otras opciones que describe:
Este tipo de solución tiene inmensos beneficios prácticos si tiene el lujo de restringir todo el acceso a los datos a través de su API transaccional. Pierde el beneficio muy importante de DRI, que es que la integridad está garantizada por la base de datos, pero en cualquier modelo de suficiente complejidad habrá algunas reglas comerciales que DRI no puede hacer cumplir .
Aconsejaría usar DRI cuando sea posible para hacer cumplir las reglas de negocio sin doblar demasiado su modelo para que eso sea posible:
Tan pronto como empiece a considerar contaminar su modelo de esta manera, creo que se está mudando al área donde el beneficio de DRI se ve compensado por las dificultades que está presentando. Considere, por ejemplo, que un error en su proceso de archivado podría, en teoría, hacer que su regla de oro (que los saldos siempre sean iguales a la suma de las transacciones) se rompa en silencio con una solución DRI .
Aquí hay un resumen de las ventajas del enfoque transaccional tal como las veo:
--editar
Para permitir el archivado sin agregar complejidad o riesgo, puede optar por mantener las filas de resumen en una tabla de resumen separada, generada continuamente (tomando prestado de @Andrew y @Garik)
Por ejemplo, si los resúmenes son mensuales:
fuente
Mella.
La idea principal es almacenar el saldo y los registros de transacciones en la misma tabla. Sucedió históricamente, pensé. Entonces, en este caso, podemos obtener el equilibrio simplemente ubicando el último registro de resumen.
Una mejor variante es la disminución del número de registros de resumen. Podemos tener un registro de saldo al final (y / o comienzo) del día. Como saben, cada banco tiene
operational day
que abrirlo y luego cerrarlo para hacer algunas operaciones de resumen para este día. Nos permite calcular fácilmente los intereses utilizando el registro de saldo diario, por ejemplo:Suerte.
fuente
Según sus requisitos, la opción 1 sería la mejor. Aunque tendría mi diseño para permitir solo inserciones en la tabla de transacciones. Y tenga el activador en la tabla de transacciones, para actualizar la tabla de saldo en tiempo real. Puede usar los permisos de la base de datos para controlar el acceso a estas tablas.
En este enfoque, se garantiza que el saldo en tiempo real esté sincronizado con la tabla de transacciones. Y no importa si se utilizan procedimientos almacenados o psql o jdbc. Puede tener su cheque de saldo negativo si es necesario. El rendimiento no será un problema. Para obtener el saldo en tiempo real, es una consulta única.
El archivado no afectará este enfoque. Puede tener una tabla de resumen semanal, mensual, anual también si es necesario para cosas como informes.
fuente
En Oracle, puede hacer esto usando solo la tabla de transacciones con una Vista materializada que se puede actualizar rápidamente y que hace la agregación para formar el saldo. Usted define el disparador en la Vista materializada. Si la Vista materializada se define con 'EN COMPROMISO', efectivamente evita agregar / modificar datos en las tablas base. El activador detecta los datos [en] válidos y genera una excepción, donde revierte la transacción. Un buen ejemplo está aquí http://www.sqlsnippets.com/en/topic-12896.html
No conozco sqlserver, pero ¿tal vez tiene una opción similar?
fuente