在Oracle表中生成银行帐户余额
我在Oracle 11g数据库中有一个accounts表和一个movements表。它们的工作方式与您希望银行账户的工作方式相同。它们的简化版本是在Oracle表中生成银行帐户余额,oracle,plsql,oracle11g,Oracle,Plsql,Oracle11g,我在Oracle 11g数据库中有一个accounts表和一个movements表。它们的工作方式与您希望银行账户的工作方式相同。它们的简化版本是 CREATE TABLE accounts ( id NUMERIC(20) NOT NULL -- PK ); CREATE TABLE movements ( id NUMERIC(20) NOT NULL, -- PK account_id NUMERIC(20) NOT NULL, -- FK to accounts
CREATE TABLE accounts (
id NUMERIC(20) NOT NULL -- PK
);
CREATE TABLE movements (
id NUMERIC(20) NOT NULL, -- PK
account_id NUMERIC(20) NOT NULL, -- FK to accounts table
stamp TIMESTAMP NOT NULL, -- Movement creation timestamp
amount NUMERIC(20) NOT NULL,
balance NUMERIC(20) NOT NULL
);
你有一个账户,一些移动是间接创建的,每个移动都有一个给定的金额。例如,我希望移动
表中包含以下数据:
| id | account_id | stamp | amount | balance |
-------------------------------------------------------------
| 1 | 1 | 2016-12-29 00:00:01 | 50.00 | 50.00 |
| 2 | 1 | 2016-12-29 00:00:02 | 80.00 | 130.00 |
| 3 | 1 | 2016-12-29 00:00:03 | -15.00 | 115.00 |
-------------------------------------------------------------
我的问题是,如何更新余额列
我在存储过程中进行插入(INSERT-INTO-movements…SELECT-FROM…
),因此可以在同一查询中、在以后的更新中或使用纯PLSQL进行插入
我可以想出两种方法:
插入后的更新
,类似(想法,未经测试):
我的问题是,它是否按顺序执行?从第一乐章到最后一乐章?或者oracle可能在没有特定顺序的情况下运行此操作,生成这样的场景:例如,第三个移动在第二个移动之前更新,因此第二个移动的余额仍然过时
游标:我可以定义一个游标并在有序的移动列表上运行循环,在每次迭代中读取帐户的上一个余额,并计算当前余额,使用更新设置它
通过这种方式,我可以确定余额是按顺序更新的,但由于性能问题,我总是避免使用游标。此存储过程每次将处理数百条记录,而移动表将存储数百万条记录。这样,性能会成为一个问题吗
我的最后一个问题是,考虑到性能,生成余额
列数据的最佳方法是什么?
编辑-动作创建说明
我想我对这部分不太清楚。在执行SP时,我正在创建多个不同帐户的多个移动,这就是为什么我提到移动创建是通过以下方式完成的
-- Some actions
INSERT INTO movements (account_id, stamp, amount, balance)
SELECT ... FROM several_tables_with_joins;
-- More actions
这就是为什么我提到余额可以在同一个查询中生成,也可以在以后的更新中生成,或者在某个注释中提到的触发器之类的其他方法中生成。我想我会将余额保留在ACCOUNTS表中。然后,在插入移动记录时,更新相应的帐户记录
“考虑到性能,生成余额列数据的最佳方法是什么?”
通常,每笔交易后对汇总列的持续维护比按需计算要花费更大的成本。然而,账户余额是一种特殊情况,因为我们确实需要在每次交易后了解它,以检查(比如)账户是否出现赤字或超过透支限额
关键的洞察是:在我们处理一个新的运动之前,我们已经知道当前的平衡。这是最新运动记录的平衡值
啊,但是我们怎么知道哪个动作记录是最新的呢?有各种不同的解决方案,但最简单的是一个丑陋的is\u latest
标志。这不仅提供了获取最新移动记录的简单方法,还提供了可锁定的目标,这在多用户环境中非常重要。我们需要确保在任何给定时间只有一个事务在操纵余额
因此,您的存储过程将类似于:
create or replace procedure new_movement
( p_account_id in movements.account_id%type
, p_amount in movements.amount%type )
is
cursor c_curr_bal (p_acct_id movements.account_id%type) is
select balance
from movements
where account_id = p_acct_id
and is_latest = 'Y'
for update of is_latest;
l_balance movements.balance%type;
new_rec movements%rowtype;
begin
open c_curr_bal(p_account_id);
fetch c_curr_bal into l_balance;
new_rec.id := movements_seq.nextval;
new_rec.account_id := p_account_id;
new_rec.stamp := systimestamp;
new_rec.amount := p_amount;
new_rec.balance := l_balance + p_amount;
new_rec.is_latest := 'Y';
update movements
set is_latest = null
where current of c_curr_bal;
insert into movements
values new_rec;
close c_curr_bal;
commit; -- need to free the lock
end new_movement;
/
is_latest
标志的另一种选择是将当前余额作为ACCOUNTS表中的一列进行维护。逻辑是一样的,只需选择ACCOUNTS表来更新当前余额即可 插入或更新后的Trigger
是解决方案,并且在触发器查询中是帐户的最后一个余额
?它不会太重吗?这在多用户环境中不起作用。如果两个用户几乎同时为同一个帐户输入不同的事务怎么办?两人在开始输入自己的交易时都不知道其他交易。您需要序列化事务,而您的用户会因此讨厌您。最好创建一个动态计算余额的视图(然后可能使其成为提交时快速刷新的MV)。锁定is\u最新
记录的句子是什么?更新
?那么它不应该在开始时,在读取最后一个余额值之前吗?另外,请查看问题中我的编辑。选择更新会在余额
记录上发出锁定。因此,当光标打开时,它在事务开始时被锁定。
create or replace procedure new_movement
( p_account_id in movements.account_id%type
, p_amount in movements.amount%type )
is
cursor c_curr_bal (p_acct_id movements.account_id%type) is
select balance
from movements
where account_id = p_acct_id
and is_latest = 'Y'
for update of is_latest;
l_balance movements.balance%type;
new_rec movements%rowtype;
begin
open c_curr_bal(p_account_id);
fetch c_curr_bal into l_balance;
new_rec.id := movements_seq.nextval;
new_rec.account_id := p_account_id;
new_rec.stamp := systimestamp;
new_rec.amount := p_amount;
new_rec.balance := l_balance + p_amount;
new_rec.is_latest := 'Y';
update movements
set is_latest = null
where current of c_curr_bal;
insert into movements
values new_rec;
close c_curr_bal;
commit; -- need to free the lock
end new_movement;
/