Piszę schemat prostej bazy danych banku. Oto podstawowe specyfikacje:
- Baza danych będzie przechowywać transakcje z użytkownikiem i walutą.
- Każdy użytkownik ma jedno saldo na walutę, więc każde saldo jest po prostu sumą wszystkich transakcji wobec danego użytkownika i waluty.
- Saldo nie może być ujemne.
Aplikacja bankowa będzie komunikować się ze swoją bazą danych wyłącznie poprzez procedury składowane.
Oczekuję, że ta baza danych będzie akceptować setki tysięcy nowych transakcji dziennie, a także wyrównywać zapytania o wyższym rzędzie wielkości. Aby szybko obsłużyć salda, muszę je wstępnie zsumować. Jednocześnie muszę zagwarantować, że saldo nigdy nie będzie sprzeczne z historią transakcji.
Moje opcje to:
Utwórz osobną
balances
tabelę i wykonaj jedną z następujących czynności:Zastosuj transakcje do tabel
transactions
ibalances
. UżyjTRANSACTION
logiki w mojej warstwie procedur składowanych, aby upewnić się, że salda i transakcje są zawsze zsynchronizowane. (Obsługiwane przez Jacka ).Zastosuj transakcje do
transactions
tabeli i uruchom wyzwalacz, który zaktualizujebalances
tabelę dla mnie o kwotę transakcji.Zastosuj transakcje do
balances
tabeli i uruchom wyzwalacz, który dodatransactions
dla mnie nowy wpis w tabeli z kwotą transakcji.
Muszę polegać na podejściach opartych na bezpieczeństwie, aby upewnić się, że nie można wprowadzić żadnych zmian poza procedurami przechowywanymi. W przeciwnym razie, na przykład, jakiś proces mógłby bezpośrednio wstawić transakcję do
transactions
tabeli, a zgodnie ze schematem1.3
odpowiednie saldo nie byłoby zsynchronizowane.Mieć
balances
widok indeksowany, który odpowiednio agreguje transakcje. Mechanizmy magazynowania gwarantują, że salda są zsynchronizowane z ich transakcjami, więc nie muszę polegać na podejściach opartych na bezpieczeństwie, aby to zagwarantować. Z drugiej strony nie mogę już wymuszać, aby salda były nieujemne, ponieważ widoki - nawet widoki indeksowane - nie mogą miećCHECK
ograniczeń. (Obsługiwany przez Denny ).Miej tylko
transactions
tabelę, ale z dodatkową kolumną do przechowywania salda efektywnego zaraz po wykonaniu transakcji. Zatem najnowszy rekord transakcji dla użytkownika i waluty zawiera również ich bieżące saldo. (Sugerowane poniżej przez Andrew ; wariant zaproponowany przez garik .)
Kiedy po raz pierwszy poradziłem sobie z tym problemem, przeczytałem te dwie dyskusje i zdecydowałem się na opcję 2
. W celach informacyjnych możesz zobaczyć jego wdrożenie od podstaw .
Czy zaprojektowałeś lub zarządzałeś taką bazą danych o profilu wysokiego obciążenia? Jakie było twoje rozwiązanie tego problemu?
Czy uważasz, że dokonałem właściwego wyboru projektu? Czy jest coś, o czym powinienem pamiętać?
Na przykład wiem, że zmiany schematu w
transactions
tabeli będą wymagać odbudowaniabalances
widoku. Nawet jeśli archiwizuję transakcje, aby baza danych była niewielka (np. Przenosząc je gdzieś indziej i zastępując je transakcjami podsumowującymi), konieczność odbudowania widoku z dziesiątek milionów transakcji przy każdej aktualizacji schematu prawdopodobnie oznacza znacznie dłuższy czas przestoju na wdrożenie.Jeśli dobrym pomysłem jest widok indeksowany, jak mogę zagwarantować, że saldo nie będzie ujemne?
Archiwizacja transakcji:
Pozwólcie mi rozwinąć nieco kwestię archiwizacji transakcji i wspomnianych powyżej „transakcji podsumowujących”. Po pierwsze, regularna archiwizacja będzie niezbędna w takim systemie o dużym obciążeniu. Chcę zachować spójność między saldami i ich historiami transakcji, jednocześnie umożliwiając przenoszenie starych transakcji gdzie indziej. Aby to zrobić, zastąpię każdą partię zarchiwizowanych transakcji podsumowaniem ich kwot na użytkownika i walutę.
Na przykład ta lista transakcji:
user_id currency_id amount is_summary
------------------------------------------------
3 1 10.60 0
3 1 -55.00 0
3 1 -12.12 0
jest archiwizowany i zastępowany następującym:
user_id currency_id amount is_summary
------------------------------------------------
3 1 -56.52 1
W ten sposób saldo z zarchiwizowanymi transakcjami zachowuje pełną i spójną historię transakcji.
źródło
Odpowiedzi:
Nie jestem zaznajomiony z rachunkowością, ale rozwiązałem kilka podobnych problemów w środowiskach typu magazyn. Przechowuję sumy bieżące w tym samym wierszu z transakcją. Używam ograniczeń, dzięki czemu moje dane nigdy nie są błędne, nawet przy wysokiej współbieżności. W 2009 roku napisałem następujące rozwiązanie :
Obliczanie sum całkowitych jest notorycznie wolne, niezależnie od tego, czy robisz to za pomocą kursora, czy za pomocą połączenia trójkątnego. Denormalizacja jest bardzo kusząca, aby przechowywać bieżące sumy w kolumnie, szczególnie jeśli często ją wybierasz. Jednak, jak zwykle podczas denormalizacji, należy zagwarantować integralność zdormalizowanych danych. Na szczęście możesz zagwarantować integralność działających sum z ograniczeniami - dopóki wszystkie ograniczenia są zaufane, wszystkie działające sumy są poprawne. W ten sposób możesz łatwo upewnić się, że bieżące saldo (sumy bieżące) nigdy nie jest ujemne - egzekwowanie innymi metodami może być również bardzo wolne. Poniższy skrypt demonstruje technikę.
źródło
Nie zezwalanie klientom na saldo mniejsze niż 0 jest regułą biznesową (która zmienia się szybko, ponieważ opłaty za np. Przekroczenie salda są sposobem, w jaki banki zarabiają większość swoich pieniędzy). Będziesz chciał poradzić sobie z tym podczas przetwarzania aplikacji, gdy wiersze zostaną wstawione do historii transakcji. Zwłaszcza, że niektórzy klienci mogą korzystać z ochrony w rachunku bieżącym, a niektórzy pobierają opłaty, a niektórzy nie pozwalają na wprowadzenie ujemnych kwot.
Jak dotąd podoba mi się to, dokąd zmierzasz, ale jeśli chodzi o rzeczywisty projekt (nie szkołę), trzeba dużo myśleć o regułach biznesowych itp. Po uruchomieniu systemu bankowego a w bieganiu nie ma zbyt wiele miejsca na przeprojektowanie, ponieważ istnieją bardzo szczegółowe prawa dotyczące osób mających dostęp do swoich pieniędzy.
źródło
Nieco innym podejściem (podobnym do drugiej opcji) do rozważenia jest posiadanie tylko tabeli transakcji z definicją:
Możesz także chcieć mieć identyfikator / zamówienie transakcji, abyś mógł obsługiwać dwie transakcje z tą samą datą i ulepszyć zapytanie dotyczące wyszukiwania.
Aby uzyskać bieżące saldo, wystarczy ostatni rekord.
Metody uzyskania ostatniego rekordu :
Cons:
Transakcje dla użytkownika / waluty będą musiały zostać zserializowane, aby zachować dokładną równowagę.
Plusy:
Edycja: Kilka przykładowych zapytań dotyczących pobierania aktualnego salda i podświetlenia con (Thanks @Jack Douglas)
źródło
SELECT TOP (1) ... ORDER BY TransactionDate DESC
Będzie bardzo trudne do wdrożenia w taki sposób, że SQL Server nie stale skanowania tabeli transakcji. Alex Kuznetsov opublikował tutaj rozwiązanie podobnego problemu projektowego, który doskonale uzupełnia tę odpowiedź.Po przeczytaniu również tych dyskusji nie jestem pewien, dlaczego zdecydowałeś się na rozwiązanie DRI w stosunku do najbardziej sensownej z innych opcji, które przedstawisz:
Takie rozwiązanie ma ogromne praktyczne korzyści, jeśli masz luksus ograniczając wszystkie dostępu do danych za pośrednictwem interfejsu API transakcyjnej. Tracisz bardzo ważną zaletę DRI, czyli integralność gwarantowana przez bazę danych, ale w każdym modelu o wystarczającej złożoności będą pewne reguły biznesowe, których DRI nie będzie w stanie egzekwować .
Radzę używać DRI tam, gdzie to możliwe, aby egzekwować reguły biznesowe bez zbytniego wyginania modelu, aby było to możliwe:
Gdy tylko zaczniesz rozważać zanieczyszczenie swojego modelu w ten sposób, myślę, że przenosisz się do obszaru, w którym korzyści wynikające z DRI przeważają nad trudnościami, które wprowadzasz. Rozważmy na przykład, że błąd w procesie archiwizacji teoretycznie może spowodować, że twoja złota reguła (która zawsze będzie równa sumie transakcji) po cichu złamie się z rozwiązaniem DRI .
Oto podsumowanie zalet podejścia transakcyjnego, jakie widzę:
--edytować
Aby umożliwić archiwizację bez zwiększania złożoności lub ryzyka, możesz pozostawić wiersze podsumowań w osobnej tabeli podsumowań, generowanej w sposób ciągły (pożyczki od @Andrew i @Garik)
Na przykład, jeśli podsumowania są miesięczne:
źródło
Nacięcie.
Główną ideą jest przechowywanie rekordów sald i transakcji w tej samej tabeli. To się zdarzyło historycznie, myślałem. Więc w tym przypadku możemy uzyskać równowagę po prostu przez zlokalizowanie ostatniego rekordu podsumowującego.
Lepszym wariantem jest zmniejszanie liczby rekordów podsumowań. Możemy mieć jeden bilans na koniec (i / lub na początku) dnia. Jak wiadomo, każdy bank musi
operational day
otworzyć i zamknąć, aby wykonać kilka operacji podsumowujących na ten dzień. Pozwala nam łatwo obliczyć odsetki , wykorzystując codzienny rekord salda, na przykład:Szczęście.
źródło
W zależności od twoich wymagań opcja 1 wydaje się najlepsza. Chociaż chciałbym, aby mój projekt pozwalał tylko na wstawianie do tabeli transakcji. I mieć wyzwalacz w tabeli transakcji, aby zaktualizować tabelę salda w czasie rzeczywistym. Możesz użyć uprawnień do bazy danych, aby kontrolować dostęp do tych tabel.
W tym podejściu saldo w czasie rzeczywistym gwarantuje synchronizację z tabelą transakcji. I nie ma znaczenia, czy używane są procedury składowane, psql czy jdbc. W razie potrzeby możesz sprawdzić saldo ujemne. Wydajność nie będzie problemem. Aby uzyskać równowagę w czasie rzeczywistym, jest to zapytanie pojedyncze.
Archiwizacja nie wpłynie na to podejście. Możesz mieć tygodniową, miesięczną, roczną tabelę podsumowań, także w razie potrzeby, np. W przypadku raportów.
źródło
W Oracle można to zrobić przy użyciu tylko tabeli transakcji z szybkim, odświeżalnym widokiem zmaterializowanym, który dokonuje agregacji w celu utworzenia salda. Zdefiniuj wyzwalacz w widoku zmaterializowanym. Jeśli widok zmaterializowany jest zdefiniowany jako „ON COMMIT”, skutecznie zapobiega dodawaniu / modyfikowaniu danych w tabelach podstawowych. Wyzwalacz wykrywa prawidłowe dane [in] i zgłasza wyjątek, w którym wycofuje transakcję. Dobrym przykładem jest tutaj http://www.sqlsnippets.com/en/topic-12896.html
Nie znam sqlserver, ale może ma podobną opcję?
źródło