Биллинг на PHP / MySQL

newARTix

Новичок
Хотелось бы обсудить архитектуру и технические решения сферического биллинга в вакууме на PHP.
Под биллингом понимается вторичная система учета заказов-транзакций-услуг-счетов.
То есть на нашем сайте-биллинге хранится информация об оказанных услугах (service), о заказанных услугах (orders), о транзакциях (billing) и о балансах пользователей (balance).
Все реальные финансовые операции проводятся в системах онлайн-платежей, типа робокассы. У нас в биллинге фиксируется только факт оплаты от этих систем и итоговый баланс пользователей.
Пользователь может как пополнять свой баланс (тогда в таблице БД order создается заказ на пополнение баланса и тут же помечается как оплаченный, при подтверждении со стороны платежной системы), так и напрямую заказывать товар/услугу (тогда в системе создается заказ, и в случае его оплаты - оказывается услуга, а заказ помечается как оплаченный).
В таблице биллинга хранится информация обо всех дебетах и кредитах пользователя. То есть при заказе услуги и оплате ее через платежную систему фиксируется дебет; при оказании услуги, фиксируется кредит. При пополнении баланса фиксируется только дебет. Таким образом по таблице биллинга мы можем в любой момент вычислить реальный баланс пользователя.
Рассматривается вариант, когда оказание услуги - мгновенное (генерация серийного номера), то есть резервировать средства на баллансе нет смысла, транзакции проходят в рамках одного запроса.

Архитектура адекватна задаче обычного интернет-магазина электронных услуг?

БД сделана на MySQL InnoDB, с целью повышения отказоустойчивости и транзакционности естественно. Кроме того, при оплате услуг с баланса, на время "вычисления реального баланса - списание с баланса" таблица billing блокируется, дабы не дать возможность параллельного вычисления/изменения балланса. Вся работа с базой происходит в PHP, без процедур и тригеров, только с помощью транзакций и блокировок.
В будущем, при росте нагрузки, планируется использовать партицирование.

Упустили ли мы что-нибудь? О чем стоит подумать? В чем ошибки решения?
 

Gas

может по одной?
у меня только замечание по поводу:
таблица billing блокируется, дабы не дать возможность параллельного вычисления/изменения балланса.
а разве select ... for update для этих целей не подойдёт, чтоб не убивать concurrency ?
 

newARTix

Новичок
Gas
изначально предполагался именно этот механизм, но я так и не понял насчет того, как именно он блокирует строки и индексы. Некогда было уточнять.
Подумал что вроде как возможна ситуация:
1) НАША СЕССИЯ С БД: вычисляем баланс с помощью SELECT ... WHERE user_id = 1 FOR UPDATE, видим что денег на счету достаточно.
2) НАША СЕССИЯ: mysql ставит блокировки на все затронутые строки
3) ПАРАЛЛЕЛЬНАЯ СЕССИЯ: вставляет новую строку с user_id = 1, то есть баланс в таблице меняется уже после его вычисления, например юзер уходит в минус, неважно по какой причине это произошло.
4) НАША СЕССИЯ: мы, думая что денег на счет юзера достаточно, оказываем услугу, и снова списываем деньги.
5) НАША СЕССИЯ: обновляем инфу о балансе и видим что что-то с ним не то, внезапно.

Теперь внимательно вчитался в мануал, там написано, "locks the rows and any associated index entries, the same as if you issued an UPDATE statement for those rows", что значит locks any associated index entries? Защитит ли этот механизм от параллельного INSERT?
 

zerkms

TDD infected
Команда форума
newARTix
Защитит. Если ты будешь делать выборку SELECT * FROM users WHERE id = 1 FOR UPDATE, потом работать с балансом пользователя, то до комита параллельная сессия не сможет получить исключительную блокировку на user.id = 1
 

newARTix

Новичок
То есть будет использоваться блокировка по внешнему индексу?
 

zerkms

TDD infected
Команда форума
newARTix
Я не знаю как физически это реализовано в mysql, но второй подобный запрос будет ждать, пока первая сессия отдаст эту запись (через комит).
 

newARTix

Новичок
Ясно. Да, тогда это более логичное и красивое решение, надо будет проверить и убрать костыль с глобальной блокировкой.
 
Сверху