我只是不确定这应该是唯一的事实来源,
根据定义,任何给定事实只能有一个事实来源。
尝试创建第二个事实来源意味着您没有事实来源,因为这将两者都归咎于可能不一致的意见的来源。
用天平单独放置一张桌子是一个微妙的领域。很容易做出忽略这一点的幼稚假设。
尽管如此,它还是有意义的,例如,有一个带有静态余额的表,这样您就有一个轻量级的选项来获取客户的余额以显示在每个页面的顶部......但是这个表有被视为(或可能成为)不值得信赖的恶棍。
这样的表特别容易出现导致竞争条件和异常的新手错误,例如将余额读入变量、以编程方式调整其值以及将新值写回数据库。这根本不是你的做法。然而,我已经多次看到它这样做了(尤其是那些仍然无法理解没有好的 ORM 这样的东西的人,因为 ORM 是一个本质上有缺陷的概念)。这样的表应该由触发器维护,而不是由应用程序代码维护,并且更新应该是原子的。
您的示例中确实显示了原子更新...
UPDATE account SET balance = balance - ? WHERE id = ? AND balance >= ?
...尽管您需要谨慎,但在这里,因为如果balance >= ? 不正确,这将成功更新 0 行...而且更糟糕的是,您的代码中的第一个占位符值为 null 的错误, SET balance = balance - NULL 会将余额设置为 NULL,因为 null 操作数(正确)会导致大多数操作评估为 null。
如果您希望将此表作为优化,那么安全设计将始终通过在执行任何重要操作(如提款或转账)之前通过交易计算审计余额来确保一致性,并在出现差异时公然拒绝继续发现,向用户解释该站点“有问题”并为您创建内部支持事件......因为这意味着您有一个错误,它允许余额表中的值与交易表不同,这是非常糟糕的事情™的标志。
这当然是通过正确使用数据库事务、理解isolation levels 并在关键部分使用locking reads 来调节的。如果余额表仅由事务表上的BEFORE INSERT 触发器修改,而不是由您的代码修改,则余额漂移的机会很少。
健全性检查仍然很关键,尽管你会从那些坚持认为触发器具有过多的性能损失并且应该避免的人那里得到所有的绞手和挥动手臂......他们完全不知道他们在说什么关于。是的,从技术上讲,汽车油箱中的燃油质量会对您的燃油经济性产生负面影响,但它是操作的关键部分。你根本不考虑。您不会带着几乎是空的油箱四处行驶。触发触发器的成本可以忽略不计,特别是考虑到它们帮助数据库自我管理的方式。不要再考虑触发器的成本。
但是像这样的表的可伸缩性问题主要由适当的索引来解决。例如,(account_id,created_on)上的事务表中的索引意味着数据库有一种方法可以立即定位一个特定帐户的所有事务,这些事务已经按它们发生的顺序排序,而无需扫描表。您应该会发现,即使在数亿行中,找到给定帐户的交易也很快,并且具有适当的索引。
SELECT sum(amount) 表示交易金额已签名,这意味着type 列有点多余,尽管您可能希望保留它并使用触发器将其捕获,要求贷方大于 0,借方小于 0 . 数据库内部的健全性检查,无论看起来多么明显/不必要,在涉及金钱时很少是一个坏主意。
您可能还应该有BEFORE DELETE 和BEFORE UPDATE 触发器来拒绝对事务表的任何更改。交易是不会改变的历史事实。通过创建第二个抵消交易来正确撤消不正确的交易。
在 MySQL Server 5.5 及更高版本中防止对给定表的所有删除的简单示例如下所示:
mysql> CREATE TRIGGER transaction_bd
BEFORE DELETE ON transaction
FOR EACH ROW
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'the transaction table does not support DELETE.';
(具有非复杂主体的触发器不需要通常的DELIMITER 声明。)