Хранение денежного баланса пользователей

filipchuk

Новичок
Хранение денежного баланса пользователей

Добрый день!
Есть задача: на сайте будет регистрация пользователей, которые смогут пополнять свой счет и соотвественно делать заказ на сайте.
С таблицей пользователей пока все ясно. Очевидно, что заказы также надо хранить в отдельной таблице с привязкой к пользователям.
Есть вопрос по "денежным" вопросам, а точнее, как хранить текущий баланс пользователя.

Пока есть 2 варианта:
1) в таблице пользователей ввести дополнительное поле баланс, и при зачислении денег его увеличивать, при заказе - уменьшать на соответствующую сумму и также вносить запись в таблицу заказы
2) сделать общую таблицу для операций пополнения и заказа, в ней будет поле Тип, которое будет определять типа операции (пополнение или заказ), и баланс высчитывать по этой таблице

Подскажите, какой вариант лучше использовать, возможно, предложите другой :)
 

Сергей Тарасов

Профессор
Для реальной системы с нагрузкой придется делать нечто комбинированное. Второй способ правильный с точки зрения проектирования БД (нормализации), но при нагрузке каждый раз вычислять баланс каждого юзера будет слишком накладно.
Для предотвращения разрушения целостности нужно использовать транзакции.
 

Mr_Max

Первый класс. Зимние каникулы ^_^
Команда форума
а. У пользователя может быть несколько "счетов". Пример - Приват24.
б. Если очень важен вопрос безопасности, то данные "пополнения счетов" могут пригодиться

каждый раз вычислять баланс каждого юзера
Данные обычно кешируют. При запросе юзером (клике на ссылку) - обновляют кеш.
 

filipchuk

Новичок
Система врядли будет высоконагруженой, если будет нагрузка - можна будет кешировать.
Несколько счетов не предусмотрены в ТЗ
=============
В ТЗ появилось новое дополнение, что нужно хранить историю пополнений счета, так что 2 вариант буду использовать
=============
Для предотвращения разрушения целостности нужно использовать транзакции.
Можна поподробнее?
 

Сергей Тарасов

Профессор
Автор оригинала: filipchuk
Система врядли будет высоконагруженой, если будет нагрузка - можна будет кешировать.
Несколько счетов не предусмотрены в ТЗ
=============
В ТЗ появилось новое дополнение, что нужно хранить историю пополнений счета, так что 2 вариант буду использовать
=============

Можна поподробнее?
Кешировать что???
Идея в том, что если как такого баланса нет, а есть только набор операций, то для вычисления баланса каждый раз, например, для проверки возможности снятия денег, нужно будет делать выборку по таблице, плюс вычислять результат. Транзакции нужны для любых операций со счетами, чтобы не получилось, так, что деньги ушли из одного места но не пришли в другое :)))
Я имел в виду, что если хранить еще текущий баланс, то операции модификации таблицы операций и этого поля выполняются одной транзакцией.
 

filipchuk

Новичок
Кешировать текущий баланс, тоесть как вы уже говорили, скомбинировать оба метода, баланс будет браться из таблицы юзеров, и пересчитываться только когда будет внесение измений в таблицу операций

Транзакции нужны для любых операций со счетами, чтобы не получилось, так, что деньги ушли из одного места но не пришли в другое
Я может чтото непонимаю, раскажу подробнее суть механизма: деньги на баланс вносит администратор при получении платежки, снимаються деньги при оформлении заказа пользователем. При внесении денег вноситься запись в таблицу операций, и при заказе вноситься запись, и в обоих случаях идет пересчет поля в таблице пользователей, в котором храниться текущий баланс. И не могу понять, зачем тут транзакции использовать? :(
 

Alexandre

PHPПенсионер
Система врядли будет высоконагруженой, если будет нагрузка - можна будет кешировать
вот как раз данные баланса кешировать нельзя :)

-~{}~ 25.12.07 15:55:

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

tf

крылья рулят
Alexandre, яндекс кеширует ... и да еще в сессии хранит (нехороший он)
 

filipchuk

Новичок
вот как раз данные баланса кешировать нельзя
может я не так назвал этот процес
суть такова, еще раз:
данные баланса храняться в отдельной таблице (ОПЕРАЦИИ), в которой могут быть записи 2 видов: плюс сумма к балансу и минус сумма к балансу
чтобы постоянно не дергать эту таблицу, в таблице ПОЛЬЗОВАТЕЛИ будет дополнительное поле - баланс, которое будет пересчитываться при вставке новых записей в таблицу ОПЕРАЦИИ. Тоесть если нету новых записей в таблице ОПЕРАЦИИ, то не имеет смысла пересчитывать баланс, а можна взять его из таблицы ПОЛЬЗОВАТЕЛИ - я это назвал "кешированием", возможно это по другому называеться, не знаю :)
Или вы чтото другое имели в виду?
 

Alexandre

PHPПенсионер
можна взять его из таблицы ПОЛЬЗОВАТЕЛИ - я это назвал "кешированием", возможно это по другому называеться, не знаю
Или вы чтото другое имели в виду?
под кешированием понимается хранение где-либо (в памяти, файловой системе или в тойже БД) промежуточных или уже оконечконечных (например уже готовый HTML) результатов взятых из БД. Возможно ты прав, но я не думаю, что это можно назвать кешированием
 

FractalizeR

Новичок
Автор оригинала: filipchuk Я может чтото непонимаю, раскажу подробнее суть механизма: деньги на баланс вносит администратор при получении платежки, снимаються деньги при оформлении заказа пользователем. При внесении денег вноситься запись в таблицу операций, и при заказе вноситься запись, и в обоих случаях идет пересчет поля в таблице пользователей, в котором храниться текущий баланс. И не могу понять, зачем тут транзакции использовать? :(
Т.е. операция внесения денег на счет пользователя и процедура оплаты пользователем заказа состоят из двух SQL операций фактически, правильно?
1. Обновляем таблицу операций
2. Пересчитываем итоговый баланс аккаунта пользователя.

Предположим, что произошла простая ситуация. Вы обновили таблицу операций, но второй запрос пересчета итогового баланса аккаунта пользователя выполниться не успел - MySQL сервер упал. Свет вырубили. И ваша база оказалась в несогласованном состоянии. Вроде бы деньги по таблице операций прошли, но баланс пользователя не изменился.

Еще вариант. Допустим, пользователь оформляет заказ. Для этого мы выполняем два действия.

1. Читаем его итоговый баланс из таблицы данных пользователя.
2а. Если баланс позволяет, делаем запись о трате денег в таблицу операций
2б. Обновляем баланс пользователя в таблице его данных.

Предположим, произошла гипотетическая ситуация. Пользователь кинул на счет много денег, позвал своего друга и стал с ним делать заказы одновременно с двух домашних компов по воплю "Давай"!. Т.е. два запроса на выполнение заказа были отправлены примерно в одно время. Сервер MySQL занят и медленно выполняет запросы. Что произойдет? В обоих скриптах выполнится первый запрос и будет определено, что денег хватает на заказ и в том и в другом случае. Далее произойдет запись в таблицу операций и пересчет баланса пользователя, который на сей раз может оказаться отрицательным.

Вот для чего нужно использовать транзакции.
 

filipchuk

Новичок
ага, теперь понял :) спасибо

-~{}~ 27.12.07 11:06:

с транзакциями не работал, примерно представляю это так:
вставка записи в таблицу ОПЕРАЦИИ и обновление поля баланс таблицы ЮЗЕРЫ происходит одной транзакцией

НАЧАЛО ТРАНЗАКЦИИ
Вставка в таблицу ОПЕРАЦИИ
Обновление таблицы ЮЗЕРЫ
КОНЕЦ ТРАНЗАКЦИИ

или не так надо?
 

FractalizeR

Новичок
Нет, все правильно, именно так и надо. Лучше, все же, наверное, сделать так:

PHP:
$db->startTransaction();
$db->select('SELECT balance FROM users WHERE id='.$userid.' FOR UPDATE');
if(!checkUserBalance()) {
$db->commit();
showBalaceExhausted();
die();
}
$db->execute('UPDATE users SET balance=.......');
$db->execute('INSERT INTO operations (...) VALUES(...)')
$db->CommitTransaction();
Приведенный выше пример, собственно, кодом на PHP не является. Скорее это псевдо-код для демонстрационных целей (как видите, результаты исполнения запросов не проверяются, try-catch отсутствует, и т.д.)

SELECT FOR UPDATE блокирует запись в таблице пользователей, запрещаяя ее обновление другими транзакциями, пока мы не закончим нашу текущую.
 

filipchuk

Новичок
или я правильно перевел алгоритм:

НАЧАЛО ТРАНЗАКЦИИ
ЕСЛИ (баланса не достаточно для оформления заказа)
1)КОНЕЦ ТРАНЗАКЦИИ
2)прерываем оформление, выдаем сообщение юзеру
ИНАЧЕ
1)отнимаем от баланса суму заказа
2)вставляем запись об операции
3)КОНЕЦ ТРАНЗАКЦИИ
 

FractalizeR

Новичок
Да, все верно. Я забыл добавить, что MySQL поддерживает транзакции начиная с версии 4.1. Тип всех таблиц, участвующих в транзакции нужно изменить на InnoDb.

COMMIT - подтверждение транзакции
ROLLBACK - отмена всей транзакции (если какая-то ошибка произошла, скажем).
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
А в чем смысл пересчета баланса при совершении операции по всем предыдущим операциям?
Я в финансовых системах делаю лог всех операций, иногда - систему двойной записи (дебет - кредит), храню остаток, который меняю.
И все, ничего пересчитывать не надо, просто хранить число без округления.
При несоответствии всегда можно "поднять" историю, но таких случаев я не помню.
Нет в банках пересчета баланса счета при каждой проводке :)
 

FractalizeR

Новичок
А про полный пересчет по таблице операций никто и не говорит. Во всяком случае, я этого не имел ввиду.
Но все же две таблицы нужно обновлять (в истории добавлять строку, а баланс прибавлять или убавлять на сумму сделки). И делать это нужно в транзакции.
 
Сверху