防止值在mysql中变为负值的最佳方法

时间:2009-01-28 18:30:15

标签: sql optimization mysql

我们有一个表,通过记录该表中的交易来维护帐户余额。即最近一行是帐户余额。

在录制提款时,我们希望确保余额​​永远不会消极。我们提出的解决方案类似于:

INSERT INTO `txns`
  (`account_id`, `prev_balance`, `txn_type`, `new_balance`,  `amount`, `description`)
SELECT 
  t.account_id, t.new_balance, $txn_type, t.new_balance - $amount, $amount, $description
FROM`txns` t
WHERE t.account_id = '$account'
  AND (select new_balance 
        FROM txns 
        WHERE account_id = '$account' 
        ORDER BY txn_id desc limit 1) >= $amount
ORDER BY txn_id desc LIMIT 1;"

但是如果ANDed子查询(我们在之前的项目中有子查询性能问题),我们对性能有点担心。这里的开发人员都不是sql专家。存款没有附加条款。

这完全在MySQL 5.0

3 个答案:

答案 0 :(得分:2)

我不能对查询的性能说些什么,抱歉。但您可能需要考虑触发器以防止“new_balance”的情况变为负面。 (因为在'new_balance'低于$ amount的情况下执行空插入会让我感到奇怪,但它可能仍然可以工作:))。

有关如何创建触发器的详细信息,请参阅documenation of MySQL 5.0

基本上你会把检查,如果NEW.new_balance是否为负的BEFORE触发器。如果是,那么您将使用“STOP ACTION”,执行中的故意错误,以中止触发器和INSERT查询。请参阅评论中提到的页面上的想法。

更新:修改一下(我在家里安装MySQL的借口)。

对于输入moneylog的每个值,我的版本有第二次写入DB的问题。

也许建议切换到存储过程。或者其他人有更好的想法,我对数据库的了解不多:)。

CREATE DATABASE triggertest;
CONNECT triggertest;

CREATE TABLE transferlog (
  account SMALLINT UNSIGNED NOT NULL ,
  amount INT NOT NULL,
  new_balance INT NOT NULL
) ENGINE=INNODB;

CREATE TABLE stopaction (
  entry CHAR(20) NOT NULL,
  dummy SMALLINT,
  UNIQUE(`entry`)
);

INSERT INTO stopaction (`entry`) VALUES ('stop');

DELIMITER #
CREATE TRIGGER nonneg_insert BEFORE INSERT ON transferlog
  FOR EACH ROW BEGIN
    INSERT INTO stopaction (`entry`)
      SELECT CASE WHEN NEW.new_balance<0 THEN 'stop'
                  ELSE 'none' END;
    DELETE FROM stopaction WHERE entry!='stop';
  END;
#

CREATE TRIGGER nonneg_update BEFORE UPDATE ON transferlog
  FOR EACH ROW BEGIN
    INSERT INTO stopaction (`entry`)
      SELECT CASE WHEN NEW.new_balance<0 THEN 'stop'
                  ELSE 'none' END;
    DELETE FROM stopaction WHERE entry!='stop';
  END;
#
DELIMITER ;


INSERT INTO transferlog (`account`, `amount`, `new_balance`)  
  VALUES (1, 1000, 1000);
INSERT INTO transferlog (`account`, `amount`, `new_balance`)
  VALUES (1, -1000, 0);
INSERT INTO transferlog (`account`, `amount`, `new_balance`)
  VALUES (1, -1000, -1000);
INSERT INTO transferlog (`account`, `amount`, `new_balance`)
  VALUES (1, 10, 20);
SELECT version();

DROP DATABASE triggertest;

也许它适合你,INSERT-Lines的输出是:

mysql> INSERT INTO transferlog (`account`, `amount`, `new_balance`)  VALUES (1, 1000, 1000);
Query OK, 1 row affected (0.03 sec)

mysql> INSERT INTO transferlog (`account`, `amount`, `new_balance`)  VALUES (1, -1000, 0);
Query OK, 1 row affected (0.02 sec)

mysql> INSERT INTO transferlog (`account`, `amount`, `new_balance`)  VALUES (1, -1000, -1000);
ERROR 1062 (23000): Duplicate entry 'stop' for key 1

mysql> INSERT INTO transferlog (`account`, `amount`, `new_balance`)  VALUES (1, 10, 20);
Query OK, 1 row affected (0.02 sec)

mysql> SELECT version();
+---------------------+
| version()           |
+---------------------+
| 5.0.67-community-nt |
+---------------------+
1 row in set (0.00 sec)

答案 1 :(得分:0)

你为什么不这样做:

INSERT INTO `txns`
  (`account_id`, `prev_balance`, `txn_type`, `new_balance`,  `amount`, `description`)
SELECT *
FROM (
 SELECT
  t.account_id, t.new_balance, $txn_type, t.new_balance - $amount, $amount, $description
 FROM `txns` t
 WHERE t.account_id = '$account'
 ORDER BY txn_id desc
 LIMIT 1
)
WHERE new_balance - $amount > 0

答案 2 :(得分:0)

我必须同意触发器的想法。如果这是一个必须遵循的会计规则,无论数据如何输入,都需要处于触发状态。

如果仅对于这种特定情况,则执行此操作,然后在SQL代码中执行此操作。我不知道mySQL但是在SQL服务器中我会将检查置于if语句中,如果满足IF条件则会使事务失败。这里的关键是不要忽略数据,而是主动使事务失败,否则用户认为数据在未满足要输入的条件时已输入。我永远不会为没有封装在事务中的finacial系统编写任何cdode,这些事务会回滚整个事务并在不满足业务规则时向用户发送错误。业务规则对于财务应用程序非常关键(并且通常应该在触发器中,以便无论数据如何被放入系统中都不会错过它们)如果所有步骤都不成功并且您是数据完整性可能是一个真正的问题不在交易中并在出现问题时回滚。