总共插入 运行

Inserting running total

在 MySQL 事务数据库中插入总数 运行 时出现问题。需要您的解决方案和意见的帮助。 Table 我的 table 结构是,

create table `wtacct` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `ACCOUNT_NO` varchar(16),
  `AMOUNT` float(16,2),
  `BALANCE` float(16,2)
);

[请注意已删除其他字段以简化示例]

我正在做交易,

插入查询

INSERT INTO wtacct (ID, ACCOUNT_NO, AMOUNT, BALANCE) 
VALUES ('', 1001, -10, 100), ('', 2002, 10, 5000);

我想要余额,
1001 号账户的余额 = 1001 号账户的最后一笔交易余额 - 10.

我的解决方案和限制

解决方案 1

在插入语句中将子查询放入余额字段:

select balance from wtacct where account_no=1001 and id in(select max(id) from wtacct where account_no=1001)

限制:Mysql 不支持插入数据 (wtacct) 的相同 table select 查询 (wtacct)。

解决方案 2

使用插入 select 语句

insert into wtacct select '' ID, 1001 ACCOUNT_NO, -10 AMOUNT, (BALANCE-10) BALANCE where account_no=1001 and id in(select max(id) from wtacct where account_no=1001)

限制: 对于第一笔交易,wtacct 中没有帐户 1001 的记录,因此 select 查询不会 return 任何第一笔交易的记录。

解决方案 3

平衡变量并在插入语句中使用它。

select @balance1001 :=balance from wtacct
  where account_no=1001 and id in(select max(id) from wtacct where account_no=1001)

select @balance2002 :=balance from wtacct
  where account_no=2002 and id in(select max(id) from wtacct where account_no=2002)

INSERT INTO wtacct (ID, ACCOUNT_NO, AMOUNT, BALANCE) 
VALUES ('', 1001, -10, @balance1001-10), ('', 2002, 10, @balance2002+10);

限制:有机会在select和插入查询执行之间及时改变平衡。也很昂贵,需要执行 3 次查询。

解决方案 4

插入然后更新余额

INSERT INTO wtacct (ID, ACCOUNT_NO, AMOUNT, BALANCE) 
VALUES ('', 1001, -10, 0);

UPDATE wtacct set balance = (ifnull(Select balance from wtacct where account_no=1001 and id in(select max(id) from wtacct where id <last_insert_id() and account_no=1001),0) -10) 
  where id =last_insert_id() and account_no=1001

........

限制:查询成本高。它需要 4(两个插入和 2 个更新)查询执行。注意 last_insert_id() 是 php 函数

解决方案 5

在插入语句中使用触发器。在触发器中,余额将更新,计算最后的交易价值和插入金额。

限制:触发器不支持事务行为,可能会失败。

请针对以上解决方案给出你的解决方案和意见。请注意,在上面的示例中,它们可能是一些语法 error/error。请无视他们。

我没有看到列出的一个很大的限制是潜在的竞争条件,其中两行同时插入 table。有可能这两个插入都会从 相同的 前一行获得当前的 "balance"。

一个问题:您是否还有单独的 "current balance" table 来为每个帐户保留当前 "balance" 的单个值?还是您只依赖上一笔交易的 "balance"。

就个人而言,我会在单独的 "account balance" table 上跟踪当前余额。我会使用 BEFORE INSERT/UPDATE 触发器来维护该行中的值,并将其用于 return 帐户的当前余额。

例如,我会定义一个这样的触发器,当一行被插入 `wtacct` 时,它会被触发 table:

CREATE TRIGGER wtacct_bi
BEFORE INSERT ON wtacct
FOR EACH ROW
BEGIN
  IF NEW.amount IS NULL THEN
     SET NEW.amount = 0;
  END IF
  ;
  UPDATE acct a
     SET a.balance = (@new_balance := a.balance + NEW.amount)
   WHERE a.account_no = NEW.account_no
  ; 
  SET NEW.balance = @new_balance
  ;
END$$

该触发器的设置...

CREATE TABLE acct 
( account_no  VARCHAR(16) NOT NULL PRIMARY KEY
, balance     DECIMAL(20,2) NOT NULL DEFAULT 0
) ENGINE=InnoDB
;

CREATE TABLE wtacct 
( id          BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT
, account_no  VARCHAR(16) NOT NULL COMMENT 'FK ref acct.account_no'
, amount      DECIMAL(20,2) NOT NULL
, balance     DECIMAL(20,2) NOT NULL 
, FOREIGN KEY FK_wtacct_acct (account_no) REFERENCES acct (account_no)
    ON UPDATE CASCADE ON DELETE RESTRICT
) ENGINE=InnoDB
;

我使用单独的 "current balance" table 的原因是给定的 account_no 只有 一个 行,并且该行保留帐户的当前余额

触发器中的 UPDATE 语句应该在正在更新的行上获得独占锁。该独占锁会阻止任何其他 UPDATE 语句同时更新同一行。 UPDATE 语句的执行将添加当前交易行中插入的“金额”到当前余额。

如果我们使用 Oracle 或 PostgreSQL,我们可以使用 RETURNING 子句来获取分配给 \'balance\' 列的值。

在 MySQL 中,我们可以使用用户定义的变量来做一个奇怪的解决方法。我们要分配给该列的新值首先分配给 user_defined 变量,然后再分配给该列。

并且我们可以将用户定义变量的值分配给插入到 `wtacct` 中的行的 `balance` 列。

这种方法的目的是在 单个 语句中检索和更新当前余额,以避免任何竞争条件。

UPDATE 语句定位该行,获取该行的排他 (X) 锁,检索当前余额(来自 \'balance\' 列的值),计算新的当前余额,并将其赋值回来到 \'balance\' 列。然后继续持有锁,直到事务完成。

触发器完成后,INSERT 语句(最初触发触发器)继续,尝试将新行插入 `wtacct`。如果失败,则回滚 INSERT 语句和触发器执行所做的所有更改,保持一切一致。

一旦会话发出 COMMIT 或 ROLLBACK,在 `acct` 中的行上持有的排他 (X) 锁将被释放,其他会话可以获得对该行的锁定 `acct`。

我已经使用 MySql

的存储过程完成了
CREATE DEFINER=`root`@`%` PROCEDURE `example_add`(IN dr Int, IN cr Int)
BEGIN

DECLARE LID int;
Declare Balance decimal(16,2);

INSERT INTO example (Debit,Credit)
VALUES (dr, cr);

SET LID = LAST_INSERT_ID();

SET Balance = (select SUM(Debit) - SUM(Credit) as Balance from example);

UPDATE Example SET  Balance = Balance WHERE ID = LID;

END

使用它 example_add(10,0) 或 example_add(0,15) 然后 select 并查看结果。