Обеспечение согласованности баланса в таблице транзакций — PostgreSQL

В базовом банковском приложении у нас есть таблица под названием «транзакции», которая включает столбцы «сумма» и «баланс». В среде вставки данных с высокой степенью параллелизма я хочу обновить столбец баланса, добавив следующее значение: текущая сумма + баланс предыдущей записи.

Пример:

таблица: транзакции

идентификатор количество баланс 1 100.00 100.00 2 50.00 150.00 3 30.00 180.00

Мы попытались использовать приведенный ниже триггер для достижения этой цели. Однако при запуске нескольких процессов, создающих новые транзакции, результаты становятся противоречивыми.

CREATE OR REPLACE FUNCTION update_balance() RETURNS TRIGGER AS $$
BEGIN
    UPDATE transactions
    SET balance = NEW.amount + COALESCE((SELECT balance FROM transactions WHERE id = NEW.id - 1), 0)
    WHERE id = NEW.id;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER update_balance_trigger
AFTER INSERT ON transactions
FOR EACH ROW
EXECUTE FUNCTION update_balance();

Мы рассматриваем возможность постановки процесса обновления в очередь, но обеспокоены тем, что это не решит проблемы согласованности и может ухудшить производительность.

Как мы можем поддерживать последовательность и производительность?

Хотя мой опыт работы связан с Oracle, а не с PostgreSQL, я вижу, что у них есть тот же механизм, который можно было бы использовать и в Oracle: эксклюзивные блокировки строк. Я подробно это описал в своем ответе. Имейте в виду, что любой тип «защиты», обеспечивающий целостность, требует некоторых накладных расходов. От ограничений столбцов до триггеров и многого другого. Таким образом, производительность не будет одинаковой, независимо от того, какой подход вы выберете. Главное — увидеть результаты тестирования производительности и найти лучший подход.

TheNomad 10.04.2024 01:11
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
1
83
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В Oracle есть функция, с помощью которой вы можете заблокировать строки , которые планируете обновить, чтобы сохранить целостность данных. Читая, я увидел, что PostgreSQL имеет аналогичную функцию и аналогичный синтаксис.

Судя по описанию, механизм кажется идентичным, и, судя по моему опыту работы с Oracle, у нас никогда не было несоответствий при его использовании, поэтому я ожидаю, что и в PostgreSQL у вас их не будет. Поскольку вы работаете с балансами, вам придется ждать изменений, иначе использование NOWAIT вернет ошибку, и вы можете «потерять» запрос на обновление баланса.

Что касается вашего другого подхода, у нас есть таблица пользовательских операций (где практически любой щелчок в приложении делает «что-то»), которая использует подход с организацией очередей. С точки зрения производительности я бы не сказал, что это повлечет за собой накладные расходы (кроме обычных в таких ситуациях), хотя профилирование использования всегда является лучшим подходом, а не предположением, однако, поскольку вы ставите в очередь обновление баланса, и если для вашего клиента выполняется много транзакций, тогда он увидит реальную величину баланса своего банковского счета позже, а не в реальном времени. Сколько «позже»? Это зависит от ситуации (опять же, вам нужно будет провести несколько тестов производительности, чтобы получить правильную оценку).

А пока попробуйте заблокировать строки с помощью SELECT 1 FROM transactions WHERE id = X FOR UPDATE. Очевидно, не забудьте отключить триггер.

Вы можете попробовать заблокировать NOWAIT и поставить в очередь только «потерянные» обновления, поскольку обычно вы обновляете каждую учетную запись отдельно, поэтому имеет смысл иметь эксклюзивные блокировки для каждой учетной записи, которая нуждается, и обновлять и ставить в очередь эту строку учетной записи, если ей нужно больше. последующие обновления для нескольких обновлений. Опять же, вам нужно будет проверить, является ли их объединение подходящим подходом для этого варианта использования.

Другие вопросы по теме