Ich schreibe das Schema für eine einfache Bankdatenbank. Hier sind die grundlegenden Spezifikationen:
- In der Datenbank werden Transaktionen für einen Benutzer und eine Währung gespeichert.
- Jeder Benutzer verfügt über einen Kontostand pro Währung. Jeder Kontostand ist also einfach die Summe aller Transaktionen mit einem bestimmten Benutzer und einer bestimmten Währung.
- Ein Saldo kann nicht negativ sein.
Die Bankanwendung kommuniziert mit ihrer Datenbank ausschließlich über gespeicherte Prozeduren.
Ich erwarte, dass diese Datenbank Hunderttausende neuer Transaktionen pro Tag akzeptiert und Abfragen in einer höheren Größenordnung ausgleicht. Um die Salden sehr schnell aufzufüllen, muss ich sie vorab aggregieren. Gleichzeitig muss ich garantieren, dass ein Kontostand niemals seiner Transaktionshistorie widerspricht.
Meine Optionen sind:
Haben Sie eine separate
balances
Tabelle und führen Sie einen der folgenden Schritte aus:Wenden Sie Transaktionen auf die Tabellen
transactions
und anbalances
. Verwenden SieTRANSACTION
Logik in meiner gespeicherten Prozedurebene, um sicherzustellen, dass Salden und Transaktionen immer synchron sind. (Unterstützt von Jack .)Wenden Sie Transaktionen auf die
transactions
Tabelle an und haben Sie einen Auslöser, der diebalances
Tabelle für mich mit dem Transaktionsbetrag aktualisiert .Wenden Sie Transaktionen auf die
balances
Tabelle an und haben Sie einen Auslöser, dertransactions
mir einen neuen Eintrag in der Tabelle mit dem Transaktionsbetrag hinzufügt .
Ich muss mich auf sicherheitsbasierte Ansätze verlassen, um sicherzustellen, dass keine Änderungen außerhalb der gespeicherten Prozeduren vorgenommen werden können. Andernfalls könnte beispielsweise ein Prozess eine Transaktion direkt in die
transactions
Tabelle einfügen, und1.3
der betreffende Saldo wäre nach dem Schema nicht synchron.Haben Sie eine
balances
indizierte Ansicht , die die Transaktionen entsprechend aggregiert. Die Speicher-Engine garantiert, dass die Salden mit ihren Transaktionen synchron bleiben, sodass ich mich nicht auf sicherheitsbasierte Ansätze verlassen muss, um dies zu gewährleisten. Andererseits kann ich nicht erzwingen, dass Salden nicht mehr negativ sind, da Ansichten - auch indizierte Ansichten - keineCHECK
Einschränkungen aufweisen können. (Unterstützt von Denny .)Haben Sie nur eine
transactions
Tabelle, aber mit einer zusätzlichen Spalte, um den Saldo direkt nach der Ausführung dieser Transaktion zu speichern. Somit enthält der letzte Transaktionsdatensatz für einen Benutzer und eine Währung auch den aktuellen Kontostand. (Vorgeschlagen von Andrew ; von Garik vorgeschlagene Variante .)
Als ich dieses Problem zum ersten Mal ansprach, las ich diese beiden Diskussionen und entschied mich für eine Option 2
. Als Referenz können Sie hier eine Bare-Bones-Implementierung sehen .
Haben Sie eine solche Datenbank mit einem hohen Lastprofil entworfen oder verwaltet? Was war Ihre Lösung für dieses Problem?
Glaubst du, ich habe die richtige Wahl getroffen? Was sollte ich beachten?
Ich weiß beispielsweise, dass Schemaänderungen an der
transactions
Tabelle eine Neuerstellung derbalances
Ansicht erfordern . Selbst wenn ich Transaktionen archiviere, um die Datenbank klein zu halten (z. B. indem ich sie an einen anderen Ort verschiebe und durch Zusammenfassungstransaktionen ersetze), bedeutet die Neuerstellung der Ansicht aus zig Millionen Transaktionen mit jeder Schemaaktualisierung wahrscheinlich eine erheblich längere Ausfallzeit pro Bereitstellung.Wie kann ich garantieren, dass kein Saldo negativ ist, wenn die indizierte Ansicht der richtige Weg ist?
Transaktionen archivieren:
Lassen Sie mich ein wenig auf die Archivierungstransaktionen und die "Zusammenfassungstransaktionen" eingehen, die ich oben erwähnt habe. Erstens ist in einem Hochlastsystem wie diesem eine regelmäßige Archivierung erforderlich. Ich möchte die Konsistenz zwischen Salden und ihren Transaktionsverläufen gewährleisten und gleichzeitig alte Transaktionen an einen anderen Ort verschieben. Dazu ersetze ich jeden Stapel archivierter Transaktionen durch eine Zusammenfassung ihrer Beträge pro Benutzer und Währung.
So zum Beispiel diese Liste von Transaktionen:
user_id currency_id amount is_summary
------------------------------------------------
3 1 10.60 0
3 1 -55.00 0
3 1 -12.12 0
wird archiviert und ersetzt durch:
user_id currency_id amount is_summary
------------------------------------------------
3 1 -56.52 1
Auf diese Weise behält ein Saldo mit archivierten Transaktionen eine vollständige und konsistente Transaktionshistorie bei.
quelle
Antworten:
Ich bin mit der Buchhaltung nicht vertraut, habe jedoch einige ähnliche Probleme in Umgebungen mit Bestandsaufnahmen gelöst. Ich speichere laufende Summen in derselben Zeile wie die Transaktion. Ich verwende Einschränkungen, damit meine Daten auch bei hoher Parallelität niemals falsch sind. Ich habe damals im Jahr 2009 folgende Lösung geschrieben :
Das Berechnen von laufenden Summen ist notorisch langsam, egal ob Sie sie mit einem Cursor oder mit einer Dreiecksverknüpfung ausführen. Es ist sehr verlockend zu denormalisieren, laufende Summen in einer Spalte zu speichern, besonders wenn Sie diese häufig auswählen. Beim Denormalisieren müssen Sie jedoch wie gewohnt die Integrität Ihrer denormalisierten Daten gewährleisten. Glücklicherweise können Sie die Integrität von laufenden Summen mit Einschränkungen garantieren. Solange alle Ihre Einschränkungen vertrauenswürdig sind, sind alle laufenden Summen korrekt. Auch auf diese Weise können Sie auf einfache Weise sicherstellen, dass der aktuelle Kontostand (laufende Summen) niemals negativ ist - die Durchsetzung durch andere Methoden kann auch sehr langsam sein. Das folgende Skript demonstriert die Technik.
quelle
Kunden ein Guthaben von weniger als 0 zu verweigern, ist eine Geschäftsregel (die sich schnell ändern würde, da Banken mit Gebühren für Überziehungskredite den größten Teil ihres Geldes verdienen). Sie möchten dies in der Anwendungsverarbeitung behandeln, wenn Zeilen in den Transaktionsverlauf eingefügt werden. Zumal einige Kunden möglicherweise einen Überziehungsschutz haben und andere Gebühren verlangen und andere die Eingabe negativer Beträge nicht zulassen.
Soweit gefällt mir, wohin Sie gehen, aber wenn dies für ein tatsächliches Projekt (nicht für die Schule) ist, müssen verdammt viele Gedanken in Geschäftsregeln usw. gesteckt werden. Sobald Sie ein Bankensystem eingerichtet haben und beim Laufen gibt es nicht viel Raum für eine Neugestaltung, da es sehr spezifische Gesetze gibt, wonach Menschen Zugang zu ihrem Geld haben.
quelle
Ein etwas anderer Ansatz (ähnlich wie bei Ihrer zweiten Option) besteht darin, nur die Transaktionstabelle mit einer Definition von:
Möglicherweise möchten Sie auch eine Transaktions-ID / Bestellung, damit Sie zwei Transaktionen mit demselben Datum bearbeiten und Ihre Abfrage verbessern können.
Um das aktuelle Guthaben zu erhalten, müssen Sie lediglich den letzten Datensatz abrufen.
Methoden, um den letzten Datensatz abzurufen :
Nachteile:
Transaktionen für den Benutzer / die Währung müssten serialisiert werden, um einen genauen Saldo zu erhalten.
Vorteile:
Bearbeiten: Einige Beispielabfragen zum Abrufen des aktuellen Guthabens und zum Hervorheben von Betrug (Danke @Jack Douglas)
quelle
SELECT TOP (1) ... ORDER BY TransactionDate DESC
wird sehr schwierig sein, das so zu implementieren, dass SQL Server die Transaktionstabelle nicht ständig durchsucht. Alex Kuznetsov hat hier eine Lösung für ein ähnliches Designproblem veröffentlicht, die diese Antwort perfekt ergänzt.Nachdem Sie diese Diskussionen ebenfalls gelesen haben, bin ich mir nicht sicher, warum Sie sich für die DRI- Lösung entschieden haben, anstatt der sinnvollsten der anderen Optionen, die Sie skizzieren:
Diese Art von Lösung bietet enorme praktische Vorteile, wenn Sie den Luxus haben, den gesamten Zugriff auf die Daten über Ihre Transaktions-API einzuschränken . Sie verlieren den sehr wichtigen Vorteil von DRI: Die Integrität wird durch die Datenbank garantiert. In jedem Modell mit ausreichender Komplexität gibt es jedoch einige Geschäftsregeln, die von DRI nicht erzwungen werden können .
Ich würde empfehlen, DRI zu verwenden, wenn dies möglich ist, um Geschäftsregeln durchzusetzen, ohne Ihr Modell zu stark zu verbiegen:
Sobald Sie erwägen, Ihr Modell auf diese Weise zu verschmutzen, bewegen Sie sich vermutlich in einem Bereich, in dem der Nutzen von DRI durch die von Ihnen eingeführten Schwierigkeiten überwiegt. Stellen Sie sich zum Beispiel vor, dass ein Fehler in Ihrem Archivierungsprozess theoretisch dazu führen könnte, dass Ihre goldene Regel (bei der der Saldo immer gleich der Summe der Transaktionen ist) stillschweigend mit einer DRI-Lösung bricht .
Hier ist eine Zusammenfassung der Vorteile des Transaktionsansatzes, wie ich sie sehe:
--bearbeiten
Um die Archivierung zu ermöglichen, ohne die Komplexität oder das Risiko zu erhöhen, können Sie Zusammenfassungstabellen in einer separaten Zusammenfassungstabelle speichern, die fortlaufend generiert wird (Ausleihe von @Andrew und @Garik).
Zum Beispiel, wenn die Zusammenfassungen monatlich sind:
quelle
Nick.
Die Hauptidee besteht darin, Saldo und Transaktionsdatensätze in derselben Tabelle zu speichern. Es ist historisch passiert, dachte ich. In diesem Fall können wir das Gleichgewicht nur durch Auffinden des letzten Zusammenfassungsdatensatzes erreichen.
Eine bessere Variante besteht darin, die Anzahl der Zusammenfassungsdatensätze zu verringern. Wir können einen Saldodatensatz am Ende (und / oder Anfang) des Tages haben. Wie Sie wissen, muss jede Bank
operational day
sie öffnen und schließen, um einige Zusammenfassungsoperationen für diesen Tag durchzuführen. Es ermöglicht uns die einfache Berechnung von Zinsen, indem wir den täglichen Saldodatensatz verwenden, zum Beispiel:Glück.
quelle
Je nach Ihren Anforderungen erscheint Option 1 am besten. Obwohl ich mein Design haben würde, um nur Einfügungen in die Transaktionstabelle zuzulassen. Und haben Sie den Auslöser für die Transaktionstabelle, um die Echtzeit-Balance-Tabelle zu aktualisieren. Sie können Datenbankberechtigungen verwenden, um den Zugriff auf diese Tabellen zu steuern.
Bei diesem Ansatz ist garantiert, dass das Echtzeitguthaben mit der Transaktionstabelle synchron ist. Dabei spielt es keine Rolle, ob gespeicherte Prozeduren oder psql oder jdbc verwendet werden. Sie können Ihren negativen Kontostand bei Bedarf überprüfen lassen. Leistung wird kein Problem sein. Um die Echtzeitbilanz zu erhalten, handelt es sich um eine Einzelabfrage.
Die Archivierung hat keinen Einfluss auf diesen Ansatz. Sie können eine wöchentliche, monatliche und jährliche Übersichtstabelle erstellen, auch wenn dies für Berichte erforderlich ist.
quelle
In Oracle können Sie dazu nur die Transaktionstabelle mit einer schnell aktualisierbaren Materialized View verwenden, die die Aggregation zur Bildung des Saldos vornimmt. Sie definieren den Auslöser für die materialisierte Ansicht. Wenn die materialisierte Ansicht mit 'ON COMMIT' definiert ist, wird das Hinzufügen / Ändern von Daten in den Basistabellen effektiv verhindert. Der Trigger erkennt die [in] gültigen Daten und löst eine Ausnahme aus, bei der die Transaktion zurückgesetzt wird. Ein schönes Beispiel ist hier http://www.sqlsnippets.com/en/topic-12896.html
Ich kenne sqlserver nicht, aber vielleicht gibt es eine ähnliche Option?
quelle