【问题标题】:Best way to prevent a value from going negative in mysql防止值在mysql中变为负数的最佳方法
【发布时间】:2009-01-28 18:30:15
【问题描述】:

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

在记录提款时,我们希望确保余额​​永远不会变为负数。我们提出的解决方案类似于:

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 上

【问题讨论】:

  • 感谢大家的反馈。我将进一步研究触发器。
  • 普林。也许我的修修补补(编辑我的文章)会给你更多的想法。

标签: sql optimization mysql


【解决方案1】:

对于查询的性能我不能说什么,抱歉。但是您可能需要考虑触发器以防止“new_balance”变成负数。 (因为如果'new_balance'低于$amount,我觉得做空插入很奇怪,但它可能仍然有效:))。

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

基本上,如果 NEW.new_balance 为负数,您会将检查放入 BEFORE 触发器中。如果是,那么您将使用“STOP ACTION”(执行中的故意错误)来中止触发器和 INSERT 查询。请参阅 cmets 中提到的页面上的想法。

更新:稍微修改了一下(我在家里安装 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)

【讨论】:

  • 这完全正确。触发器非常适合这种情况,因此只需让触发器使语句执行失败并捕获错误。
  • 如果余额变为负数,附加子句会阻止插入发生,因此它不是空插入(我查看受影响的行数,如果没有插入行,则会引发异常)跨度>
  • 至于触发器,我已经看了一点,但棘手的部分似乎是导致人为错误中止插入。
【解决方案2】:

你为什么不这样做:

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

【讨论】:

    【解决方案3】:

    我必须同意触发的想法。如果这是无论如何输入数据都必须遵循的会计规则,则它需要在触发器中。

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

    【讨论】:

    • MySQL 不做检查约束(否则,我会使用它)。我目前的解决方案是对余额进行单行原子更新。
    猜你喜欢
    • 2020-11-21
    • 2011-01-07
    • 2021-11-23
    • 2012-12-10
    • 2011-04-01
    • 2016-09-13
    • 2013-10-05
    • 2012-08-20
    • 1970-01-01
    相关资源
    最近更新 更多