powered by simpleCommunicator - 2.0.61     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / Проектирование БД [игнор отключен] [закрыт для гостей] / Как лучше организовать баланс пользователя
12 сообщений из 12, страница 1 из 1
Как лучше организовать баланс пользователя
    #36394064
nodir_azam
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Здравствуйте, уважаемые SQL-разработчики.
С Наступающим Новым годом Вас!

У меня вопрос, пусть тривиальный, но все же актуальный... Нужно организовать простейший биллинг. Отслеживать баланс пользователя, поступления на счет и списание средств. Без использования триггеров. У меня есть два варианта, помогите выбрать оптимальный.
1-й Вариант.

Есть таблицы:
- Пользователи,
- Текущий баланс пользователя,
- Операции

Таблица "Операции" содержит поля (ID int identity(1,1) PK, Summ real, Operation bit, DateOperation datetime default(getdate())).

Поступления на счет имеют Operation - 1, списания Operation - 0. Баланс считается каждые 0,5 минуты. То есть, операция списания, вставляется в базу каждые 0,5 минут, при этом могут быть параллельные транзакции, если пользователи параллельно звонят кому-то, возможно транзакции
будут блокировать друг друга. При списании денег, пересчитываем баланс за последний месяц
(или другой период) и фиксируем в таблице "Текущий баланс пользователя".
Если баланс пользователя меньше чем стоимость звонка на 0,5 минут,
блокируем пользователя (ставим метку в таблице "Пользователи").

2-й Вариант.

Есть таблицы:
- Пользователи,
- Операции
- Операции (архив)

Таблица "Операции" содержит поля (ID int identity(1,1) PK, Summ real, Operation bit, DateOperation datetime default(getdate())).

Поступления на счет имеют Operation - 1, списания Operation - 0. Баланс считается каждые 0,5 минут. Операция списания вставляется в базу каждые 0,5 минут, при этом могут быть параллельные транзакции, если пользователи параллельно звонят кому-то, возможно таблицы будут заблокированы. При списании денег, пересчитываем на лету баланс. Если баланс пользователя меньше чем стоимость звонка на 0,5 минут, блокируем пользователя (ставим метку в таблице "Пользователи"). Вешаем JOB, который периодически переносит строки с таблицы "Операции" в таблицу "Операции (архив)". Баланс делаем как View или Function.
Например,
Код: plaintext
select * from dbo.GetBalance(@DateTime)
или
Код: plaintext
1.
select * from dbo.ViewMonthlyBalance
where dateOperation between @dateStart and @dateEnd

Нужно чтобы работало быстро.
Как быть, что делать и как правильно делать???
Спасибо за предложенные варианты!
...
Рейтинг: 0 / 0
Как лучше организовать баланс пользователя
    #36394069
nodir_azam
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
авторТаблица "Операции" содержит поля (ID int identity(1,1) PK, Summ real, Operation bit, DateOperation datetime default(getdate())).

Забыл поле. Извините!

Таблица "Операции" содержит поля
Код: plaintext
1.
2.
3.
4.
5.
ID int identity( 1 , 1 ) PK, 
UserId int, 
Summ real, 
Operation bit, 
DateOperation datetime default(GetDate())


Модератор: Тема перенесена из форума "Microsoft SQL Server".
...
Рейтинг: 0 / 0
Как лучше организовать баланс пользователя
    #36394156
vino
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
nodir_azam...поступления на счет и списание средств. Без использования триггеров. У меня есть два варианта, помогите выбрать оптимальный.
1-й Вариант.
...Баланс считается каждые 0,5 минуты. ...при этом могут быть параллельные транзакции
это нормальный вариант, если забыть, что нельзя использовать триггер и отказаться от "каждые 0,5 минуты". К сведению, блокировки при параллельных транзакциях - нормальное явление
А вот это явное противоречие насчет "каждые 0,5 минут":
nodir_azamОперация списания вставляется в базу каждые 0,5 минут, при этом могут быть параллельные транзакции...
nodir_azam2-й Вариант...
...При списании денег, пересчитываем на лету баланс.
...Нужно чтобы работало быстро...
это вообще не вариант для online, тем более что четко не сформулирован.
(честно говоря, не встречал еще реального процесса, который бы просто генерил сразу одну окончательную операцию списания, так как в жизни возможны отмены транзакций)

В вашем случае как раз два взаимосвязанных объекта: набор операций и их агрегация в виде суммы. Более того, вами не допущен вариант отрицательного баланса, т.е. на каждую операцию списания нужна проверка и изменение в одной транзакции, которая и обеспечит целостность.
Другое дело, что транзакции по разным пользователям логически не должны блокировать друг друга никогда - это первое возможное ускорение.
Второе, согласно отсутствию отрицательного баланса, транзакция списания со счета может быть прервана, отменяя предоставление услуги, что нужно учитывать при разработке.

Так как вы не указали версию ПО, думаю, вам лучше перейти с данным вопросом в "Проектирование"
...
Рейтинг: 0 / 0
Как лучше организовать баланс пользователя
    #36394181
nodir_azam
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
vino,

Да прошу прощения.

Ладно, задам вопрос по другому.

Биллинг учитывает стоимость звонков Web-call.
Фактически участвуют в диалоге две "ноги".
"Нога-1" от сервера до телефона самого пользователя,
который совершает звонок, "Нога-2" до конечного телефона (абонента).

Последовательность вызовов при звонке следующая:
1. Старт задачи звонка, посылается запрос с идентификатором абонента (acc1@domain1)
и телефоном From в e.164 (+7495123456) и телефоном To в e.164 (+4887585576).
Биллинг возвращает возможность такого звонка (если денег у инициатора хватит денег хотя бы на минуту).
2. Стартует звонок на телефон From. Пользователь поднимает трубку. Посылается запрос в биллинг START1.
3. Далее, каждые пол минуты, посылается в биллинг запросы PING1 (возвращается ответ - 1, можно продолжать звонок, 0 - средства на исходе, прерываем).
4. Если пользователь в этот момент бросил трубку, посылается запрос END1 (Биллинг считает окончательную стоимость звонка).
5. Стартует звонок на телефон To. Абонент (конечный пользователь) поднимает трубку. Посылается запрос START2.
6. Далее каждые пол минуты посылается запрос PING2.(возвращается ответ - 1, можно продолжать звонок, 0 - средства у инициатора звонка на исходе, прерываем)
7. Окончание разговора с любой стороны, посылка запроса END2.

Как эту логику перенести в таблицы БД.

Сервер Windows, БД - MS SQL 2005
...
Рейтинг: 0 / 0
Как лучше организовать баланс пользователя
    #36394209
nodir_azam
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
допишу,
каждые 0,5 минут, кроме записи в таблицу о списании средств,
надо отдавать для IP PBX ответ, прерывать звонок или продолжать,
есть деньги на счету или нет...
...
Рейтинг: 0 / 0
Как лучше организовать баланс пользователя
    #36394442
SERG1257
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Предлагаю начать с решения задачи в лоб (самым простым способом)
мне больше нравится вариант 2
Единственное я не понял зачем нужен Operation bit, почему это не регулируется знаком Summ
Второе тип данных у summ лучше объявить int и считать баланс в копейках (милликопейках)
Третье зачем параметр типа даты в select * from dbo.GetBalance(@DateTime) там должен быть код абонента @UserId int

Код: plaintext
1.
2.
3.
4.
5.
create function dbo.GetBalance(@UserId int) 
return int as 
declare @result int
select @result=sum(summ) from operation where userId=@UserId 
return result
При условии кластеризации по userId функция будет работать достаточно быстро.
Нужна таблица Звонки

То есть
1. Старт задачи звонка, посылается запрос с идентификатором абонента (acc1@domain1)
и телефоном From в e.164 (+7495123456) и телефоном To в e.164 (+4887585576).
Запрашиваем dbo.GetBalance(@UserId int)
Заначиваем тариф (сумма списания за полминуты разговора), оно нам скорее всего понадобится
Вставляем в таблицу звонков

2. Стартует звонок на телефон From. Пользователь поднимает трубку. Посылается запрос в биллинг START1.
Транзакция
Вставляем строчку со списанием за полминуты (минуту) разговора (сумма была рассчитана заранее чтобы не тормозить транзакцию) и кодом звонка
Запрашиваем dbo.GetBalance(@UserId int)
Коммит

3. Далее, каждые пол минуты, посылается в биллинг запросы PING1 (возвращается ответ - 1, можно продолжать звонок, 0 - средства на исходе, прерываем).
Транзакция
Вставляем строчку со списанием за полминуты (минуту) разговора
Запрашиваем dbo.GetBalance(@UserId int)
Коммит

4. Если пользователь в этот момент бросил трубку, посылается запрос END1 (Биллинг считает окончательную стоимость звонка).
Обновляем таблицу звонков
Считаем сумму операций по конкретному звонку.

5,6,7 Аналогично

Такая схема УЖЕ должна обеспечить приемлимую производительность, но ее можно улучшить
В свободное время (раз в день, месяц, год) можно заменить блок записей у абонента на одну - входящее сальдо и переместить его (блок) в архив.
...
Рейтинг: 0 / 0
Как лучше организовать баланс пользователя
    #36394474
nodir_azam
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
SERG1257,

Я сделал так

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
CREATE TABLE [dbo].[UserBalance](
	[Id] [int] NOT NULL,
	[UserId] [int] NOT NULL,
	[Total] [real] NOT NULL,
	[DateCounting] [datetime] NOT NULL CONSTRAINT [DF_UserBalance_DateCounting]  DEFAULT (getdate()),
 CONSTRAINT [PK_UserBalance] PRIMARY KEY CLUSTERED 
(
	[Id] ASC
)WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
ALTER function [dbo].[GetBalance] (@userid int, @dateCalc datetime)
returns real
as
begin
   declare @lastTopUp real
   declare @balance real
   declare @withdrawal real
   set @lastTopUp =  0  
   set @withdrawal =  0 
   set @balance =  0 
   declare @lastTopUpDate datetime

   -- получаем последнее пополнение 
   select top  1  
   @lastTopUp = isnull(Summ, 0 ) + isnull(TotalInCurrentDate, 0 ), 
   @lastTopUpDate = DateCreate
   from dbo.UserOperations with (nolock)
   where Operation =  1  and DateCreate <= @dateCalc and UserId = @userid
   order by DateCreate desc

   -- получаем все списания, после последнего пополнения 
   select 
   @withdrawal = isnull(sum(Summ), 0 )
   from dbo.UserOperations with (nolock)
   where Operation =  0  and DateCreate >= @lastTopUpDate and UserId = @userid
   group by Operation 

   -- ну и собственно считаем баланс 
   set @balance = @lastTopUp - @withdrawal
   if @balance is null 
      return  0  
   return @balance
end
 

---

-- списание 
ALTER proc [dbo].[UserPayment]
@userId int,
@amount real 
as
begin 
begin transaction
    declare @balance real
    declare @ok bit 
    set @ok =  0 
    select @balance = dbo.GetBalance (@userId, getdate()) 
    if @balance >  0  and @balance > @amount
    begin
        set @ok =  1  
		INSERT INTO 
		[dbo].[UserOperations]
		   ([UserId]
		   ,[Operation]
		   ,[Summ]
		   ,[TotalInCurrentDate]
		   ,[DateCreate])
		VALUES
		   (@userId
		   , 0 
		   ,@amount
		   ,dbo.GetBalance (@userId, getdate())
		   ,getdate())
     end 
     select @balance = dbo.GetBalance (@userId, getdate()) 
	 update dbo.Users set CurrentBalance = @balance,
     Enabled = case when @balance >  0  then  1  else  0  end 
     where Id = @userid  
     select @balance as [balance], @ok as [executed], 
         case when @ok =  1  then  0  else  1  end as notEnoughMoney
commit transaction
end 
----

--- пополнение 

ALTER proc [dbo].[TopUpAccount]
@userId int,
@amount real 
as
begin 
begin transaction
	INSERT INTO 
	[dbo].[UserOperations]
	   ([UserId]
	   ,[Operation]
	   ,[Summ]
	   ,[TotalInCurrentDate]
	   ,[DateCreate])
	VALUES
	   (@userId
	   , 1 
	   ,@amount
	   ,dbo.GetBalance (@userId, getdate())
	   ,getdate())
     declare @balance real
     select @balance = dbo.GetBalance (@userId, getdate())
     update dbo.Users set CurrentBalance = @balance,
     Enabled = case when @balance >  0  then  1  else  0  end 
     where Id = @userid  
     select @balance
commit transaction
end 
---


Забил 4 пользователей
и 500 000 операций ... работает шустро...
может можно лучше???
...
Рейтинг: 0 / 0
Как лучше организовать баланс пользователя
    #36394479
nodir_azam
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
теперь надо все это привязать к вызовам со стороны телефонии...
не забыть про таблицу тарифов...
...
Рейтинг: 0 / 0
Как лучше организовать баланс пользователя
    #36394491
nodir_azam
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
жаль нельзя редактировать посты, не те таблицы привёл



Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
CREATE TABLE [dbo].[UserOperations](
	[Id] [int] IDENTITY( 1 , 1 ) NOT NULL,
	[UserId] [int] NULL,
	[Operation] [bit] NOT NULL CONSTRAINT [DF_UserOperations_Operation]  DEFAULT (( 0 )),
	[Summ] [real] NOT NULL,
	[TotalInCurrentDate] [real] NOT NULL,
	[DateCreate] [datetime] NOT NULL CONSTRAINT [DF_UserOperations_DateCreate]  DEFAULT (getdate()),
 CONSTRAINT [PK_UserOperations] PRIMARY KEY CLUSTERED 
(
	[Id] ASC
)WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]


CREATE TABLE [dbo].[PbxCommands](
	[Id] [int] NOT NULL,
	[Command] [char]( 50 ) COLLATE Cyrillic_General_CI_AS NOT NULL,
	[Rate] [real] NULL,
	[Amount] [real] NULL,
	[CountyId] [int] NULL,
	[FromNumber] [char]( 50 ) COLLATE Cyrillic_General_CI_AS NULL,
	[ToNumber] [char]( 50 ) COLLATE Cyrillic_General_CI_AS NULL,
	[DateCommand] [datetime] NULL,
	[Response] [bit] NULL,
 CONSTRAINT [PK_PbxCommands] PRIMARY KEY CLUSTERED 
(
	[Id] ASC
)WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

...
Рейтинг: 0 / 0
Как лучше организовать баланс пользователя
    #36394497
SERG1257
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Хозяин барин.
Вы
1 считаете баланс ДВА раза
2 при подсчете баланса возитесь с датой
3 обновляете таблицу пользователей с текущим денормализованным балансом, то есть принимаете на себя всю транзакционную головную боль
4 неправильно именуете функции (UserPayment ну никак не похоже на списание)
5 ведете нарастающий итог (это задача отчета)

nodir_azam Забил 4 пользователей и 500 000 операций ... работает шустро... Самое веселое когда тонны пользователей одновременно пытаются вставлять/обновлять
nodir_azam может можно лучше???Как лучше на мой взгляд я отвечал выше.
...
Рейтинг: 0 / 0
Как лучше организовать баланс пользователя
    #36394506
nodir_azam
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
SERG1257,

Согласен...
Ну с датой вожусь, потому что мне не нужна вся история пользователя, а только
от последнего пополнения, и нарастающий итог тоже для этого

Есть альтернативное решение как исключить не нужную историю операций?
...
Рейтинг: 0 / 0
Как лучше организовать баланс пользователя
    #36394513
nodir_azam
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Кстати, еще вопрос,
есть какая нибудь тулза, которая позволяет тестировать нагрузку на БД,
создавая одновременные пользовательские сессии. Чтобы самому не писать
консольку...
...
Рейтинг: 0 / 0
12 сообщений из 12, страница 1 из 1
Форумы / Проектирование БД [игнор отключен] [закрыт для гостей] / Как лучше организовать баланс пользователя
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]