Я пишу схему для простой банковской базы данных. Вот основные характеристики:
- База данных будет хранить транзакции против пользователя и валюты.
- У каждого пользователя есть один баланс на валюту, поэтому каждый баланс - это просто сумма всех транзакций с данным пользователем и валютой.
- Баланс не может быть отрицательным.
Приложение банка будет связываться с базой данных исключительно через хранимые процедуры.
Я ожидаю, что эта база данных будет принимать сотни тысяч новых транзакций в день, а также балансировать запросы более высокого порядка. Чтобы быстро подвести баланс, мне нужно предварительно агрегировать их. В то же время я должен гарантировать, что баланс никогда не противоречит истории его транзакций.
Мои варианты:
Создайте отдельную
balances
таблицу и выполните одно из следующих действий:Применить операции для обоих
transactions
иbalances
таблиц. ИспользуйтеTRANSACTION
логику в слое моей хранимой процедуры, чтобы гарантировать, что сальдо и транзакции всегда синхронизированы. (При поддержке Джек .)Примените транзакции к
transactions
таблице и получите триггер, который обновитbalances
для меня таблицу с суммой транзакции.Примените транзакции к
balances
таблице и получите триггер, который добавляет новую запись вtransactions
таблицу для меня с суммой транзакции.
Я должен полагаться на подходы, основанные на безопасности, чтобы убедиться, что никакие изменения не могут быть внесены за пределы хранимых процедур. В противном случае, например, какой-то процесс мог бы напрямую вставить транзакцию в
transactions
таблицу, и по схеме1.3
соответствующий баланс был бы не синхронизирован.Иметь
balances
индексированное представление, которое соответствующим образом агрегирует транзакции. Механизмы хранения гарантируют, что сальдо будет синхронизировано с их транзакциями, поэтому мне не нужно полагаться на подходы, основанные на безопасности, чтобы гарантировать это. С другой стороны, я больше не могу принудить баланс быть неотрицательным, так как представления - даже индексированные представления - не могут иметьCHECK
ограничений. (При поддержке Денни .)Имейте только
transactions
таблицу, но с дополнительным столбцом для хранения баланса, действующего сразу после выполнения этой транзакции. Таким образом, последняя запись транзакции для пользователя и валюты также содержит их текущий баланс. ( Предложено Эндрю ниже ; вариант предложен Гариком .)
Когда я впервые решил эту проблему, я прочитал эти две дискуссии и выбрал вариант 2
. Для справки, вы можете увидеть его реализацию здесь .
Вы разработали или управляли такой базой данных с высокой нагрузкой? Каково было ваше решение этой проблемы?
Как вы думаете, я сделал правильный выбор дизайна? Что я должен иметь в виду?
Например, я знаю, что для изменения схемы
transactions
таблицы потребуется перестроитьbalances
представление. Даже если я архивирую транзакции, чтобы сохранить базу данных небольшой (например, перемещая их в другое место и заменяя их сводными транзакциями), необходимость перестраивать представление для десятков миллионов транзакций при каждом обновлении схемы, вероятно, будет означать значительно большее время простоя на развертывание.Если индексированное представление - это путь, как я могу гарантировать отсутствие отрицательного баланса?
Архивация транзакций:
Позвольте мне немного подробнее рассказать об архивации транзакций и «сводных транзакциях», о которых я упоминал выше. Во-первых, в такой системе с высокой нагрузкой необходимо регулярное архивирование. Я хочу поддерживать согласованность между балансами и историями их транзакций, позволяя перемещать старые транзакции в другое место. Для этого я заменю каждую партию заархивированных транзакций сводкой их сумм на пользователя и валюту.
Так, например, этот список транзакций:
user_id currency_id amount is_summary
------------------------------------------------
3 1 10.60 0
3 1 -55.00 0
3 1 -12.12 0
архивируется и заменяется этим:
user_id currency_id amount is_summary
------------------------------------------------
3 1 -56.52 1
Таким образом, баланс с архивированными транзакциями поддерживает полную и непротиворечивую историю транзакций.
Ответы:
Я не знаком с бухгалтерским учетом, но я решил некоторые подобные проблемы в средах инвентарного типа. Я храню промежуточные итоги в одной строке с транзакцией. Я использую ограничения, так что мои данные никогда не ошибаются даже при высоком параллелизме. Я написал следующее решение еще в 2009 году :
Вычисление промежуточных итогов печально известно медленно, независимо от того, делаете ли вы это с помощью курсора или треугольного соединения. Очень заманчиво денормализовать, хранить промежуточные итоги в столбце, особенно если вы часто его выбираете. Однако, как обычно, когда вы денормализуете, вы должны гарантировать целостность ваших денормализованных данных. К счастью, вы можете гарантировать целостность промежуточных итогов с ограничениями - если все ваши ограничения являются доверенными, все промежуточные итоги верны. Таким образом, вы также можете легко гарантировать, что текущий баланс (промежуточные итоги) никогда не будет отрицательным - применение других методов также может быть очень медленным. Следующий скрипт демонстрирует технику.
источник
Запретить клиентам баланс менее 0 - это бизнес-правило (которое быстро изменится, поскольку комиссионные за такие вещи, как перерасход средств, - это то, как банки делают большую часть своих денег). Вы захотите обработать это при обработке приложения, когда строки вставляются в историю транзакций. Тем более, что некоторые клиенты могут получить защиту от овердрафта, а некоторые получают комиссионные, а некоторые не допускают ввода отрицательных сумм.
До сих пор мне нравится, куда вы идете с этим, но если это для реального проекта (не для школы), нужно чертовски много думать о бизнес-правилах и т. Д. После того, как вы создали банковскую систему и здесь не так много места для перепроектирования, поскольку существуют очень конкретные законы о людях, имеющих доступ к своим деньгам.
источник
Немного другой подход (аналогичный вашему второму варианту), который нужно рассмотреть, состоит в том, чтобы иметь только таблицу транзакций с определением:
Вам также может потребоваться идентификатор транзакции / ордер, чтобы вы могли обрабатывать две транзакции с одинаковой датой и улучшить свой поисковый запрос.
Чтобы получить текущий баланс, все, что вам нужно, это последняя запись.
Способы получения последней записи :
Минусы:
Транзакции для пользователя / валюты должны быть сериализованы для поддержания точного баланса.
Плюсы:
Изменить: Некоторые примеры запросов на получение текущего баланса и выделить кон (Спасибо @Джек Дуглас)
источник
SELECT TOP (1) ... ORDER BY TransactionDate DESC
будет очень сложно реализовать таким образом, что SQL Server не будет постоянно сканировать таблицу транзакций. Алексей Кузнецов разместил здесь решение похожей проблемы дизайна, которая отлично дополняет этот ответ.Прочитав эти обсуждения, я не уверен, почему вы выбрали решение DRI, а не наиболее разумные из перечисленных вами вариантов:
Такое решение имеет огромные практические преимущества, если у вас есть возможность ограничить весь доступ к данным через ваш транзакционный API. Вы теряете очень важное преимущество DRI, заключающееся в том, что целостность гарантируется базой данных, но в любой модели достаточной сложности будут некоторые бизнес-правила, которые не могут быть реализованы DRI .
Я бы посоветовал использовать DRI, где это возможно, для обеспечения соблюдения бизнес-правил без чрезмерного изменения модели, чтобы сделать это возможным:
Как только вы начнете рассматривать вопрос о загрязнении вашей модели таким образом, я думаю, что вы движетесь в область, где преимущества DRI перевешиваются трудностями, которые вы вводите. Рассмотрим, например, что ошибка в вашем процессе архивации может теоретически привести к тому, что ваше золотое правило (баланс всегда равен сумме транзакций) может молча нарушиться с помощью решения DRI .
Вот краткое изложение преимуществ транзакционного подхода, как я их вижу:
--редактировать
Чтобы разрешить архивирование без добавления сложности или риска, вы можете хранить итоговые строки в отдельной сводной таблице, генерируемой непрерывно (заимствуя из @Andrew и @Garik)
Например, если резюме являются ежемесячными:
источник
Ник.
Основная идея заключается в хранении баланса и записей транзакций в одной таблице. Это случилось исторически, как я думал. Таким образом, в этом случае мы можем получить баланс, просто найдя последнюю сводную запись.
Лучшим вариантом является уменьшение количества кратких отчетов. У нас может быть одна запись баланса в конце (и / или начале) дня. Как вы знаете, каждый банк должен
operational day
открыть, а затем закрыть его, чтобы выполнить некоторые сводные операции за этот день. Это позволяет нам легко рассчитывать проценты , используя ежедневную запись баланса, например:Удача.
источник
Исходя из ваших требований, вариант 1 будет лучшим. Хотя я хотел бы, чтобы мой дизайн разрешал только вставки в таблицу транзакций. И иметь триггер в таблице транзакций, чтобы обновить таблицу баланса в реальном времени. Вы можете использовать разрешения базы данных для управления доступом к этим таблицам.
При таком подходе баланс в реальном времени гарантированно синхронизируется с таблицей транзакций. И не имеет значения, используются ли хранимые процедуры, psql или jdbc. При необходимости вы можете проверить отрицательный баланс. Производительность не будет проблемой. Чтобы получить баланс в реальном времени, это одиночный запрос.
Архивация не повлияет на этот подход. Вы можете иметь еженедельную, ежемесячную, годовую сводную таблицу, а также при необходимости для таких вещей, как отчеты.
источник
В Oracle вы можете сделать это, используя только таблицу транзакций с быстро обновляемым Материализованным представлением, которое выполняет агрегирование для формирования баланса. Вы определяете триггер в материализованном представлении. Если материализованное представление определено с помощью «ON COMMIT», оно эффективно предотвращает добавление / изменение данных в базовых таблицах. Триггер обнаруживает [in] допустимые данные и вызывает исключение, когда он откатывает транзакцию. Хороший пример здесь http://www.sqlsnippets.com/en/topic-12896.html
Я не знаю sqlserver, но, возможно, у него есть аналогичная опция?
источник