简体   繁体   English

PHP Mysql-使用分组依据的交易

[英]PHP Mysql - transactions using group by

i am trying to create a banking system in PHP and mysql. 我正在尝试在PHP和mysql中创建银行系统。 Right now i have a transactions table that has columns like: 现在我有一个事务表,其中包含诸如:

id (bigint) | account_id (int) | amount (decimal(10,2) SIGNED) | type (credit/debit) | created_on (timestamp)

so to get a total account balance the query would look like: 因此,要获得总帐户余额,查询将类似于:

select sum(amount) from table where account_id = ? group by account_id order by created_on asc

we need to have a transactions table that breaks down every transaction within the system. 我们需要有一个事务表来分解系统中的每个事务。 Im just not sure this should be the single source of truth, especially considering the need for transactions with depositing/withdrawing/sending money 我只是不确定这是否是事实的唯一来源,尤其是考虑到需要进行存入/提取/发送资金的交易时

Does the single transaction table scale? 单个交易表可扩展吗? what happens with a withdraw, where we need to get the balance, check against the withdrawal amount and then either insert into the transaction table or fail? 提款会发生什么,我们需要获取余额,对照提款金额进行核对,然后插入交易表中,否则将失败?

Would it be easier just to have two tables, one for transactions, and another for account_balances, that way for withdrawals we just need to do: 只拥有两个表,一个用于交易,另一个用于account_balances,这样取款,我们只需要这样做会更容易:

UPDATE account SET balance = balance - ? WHERE id = ? AND balance >= ?

Thanks Brian 谢谢布莱恩

Im just not sure this should be the single source of truth, 我只是不确定这是否是事实的唯一来源,

There can be only one source of truth about any given fact, by definition. 根据定义,关于任何给定事实,只有一个真理来源。

Attempting to create a second source of truth means you have no source of truth, because it relegates both to mere sources of potentially inconsistent opinions. 尝试创建第二个真理源意味着您没有真理源,因为这将二者都归结为仅可能产生不一致意见的源头。

Keeping a separate table with balances is delicate territory. 用天平保持单独的桌子是微妙的领域。 It's easy to make naïve assumptions that overlook this. 容易做出天真的假设而忽略了这一点。

In spite of this, it could make sense, for example, to have a table with static balances so that you have a lightweight option for fetching the customer's balance for display at the top of each page... but this table has to be treated like the untrustworthy villain that it is (or can become). 尽管如此,例如,有一张带有静态余额的表格可能是有意义的,以便您有一个轻量级的选项来获取客户的余额以显示在每页的顶部...但是必须处理此表格就像它是(或可能成为)不可信任的恶棍。

Tables like this are particularly prone to novice mistakes that lead to race conditions and anomalies, such as reading the balance into a variable, programmatically adjusting its value, and writing the new value back to the database. 像这样的表特别容易出现新手错误,从而导致比赛条件和异常,例如将天平读取到变量中,以编程方式调整其值,并将新值写回到数据库中。 That's simply not how you do it. 那根本不是你怎么做的。 Yet, I've seen it done that way so many times (particularly by people who still fail to understand that there is no such thing as a good ORM, because ORMs are an intrinsically flawed concept). 但是,我已经看过它做过这么多次了(特别是对于那些仍然不了解没有一个好的ORM的人,因为ORM是一个本质上有缺陷的概念)。 Such a table should be maintained by triggers, not by application code, and updates should be atomic. 这样的表应该由触发器维护,而不是由应用程序代码维护,并且更新应该是原子的。

You did show an atomic update in your example... 您确实在示例中显示了原子更新...

UPDATE account SET balance = balance - ? WHERE id = ? AND balance >= ?

...although you need to use caution, here, because this will successfully update 0 rows if balance >= ? ...尽管您需要格外小心,但在这里,因为如果balance >= ?它将成功更新0行balance >= ? is not true... and, much worse, a bug in your code where the first placeholder value is null, SET balance = balance - NULL will set the balance to NULL, since a null operand (correctly) causes most operations to evaluate to null. 是不正确的……更糟糕的是,您代码中的第一个占位符值为null的错误, SET balance = balance - NULL会将balance设置为NULL,因为空操作数(正确)会导致大多数操作求和为空值。

If you want this table as an optimization, then safe design would always ensure consistency by auditing the balance with a calculation from the transactions before doing anything important (like a withdrawal or transfer), and blatantly refuse to proceed if a discrepancy is found, explaining to the user that the site is "having problems" and creating an internal support incident for you... because this means you have a bug that is allowing the values in the balance table to diverge from the transaction table, which is a sign of Very Bad Things™. 如果您希望此表是一个优化表,那么安全的设计将始终通过在进行任何重要的操作(例如提款或转账)之前用交易中的计算结果审核余额来确保一致性,并且如果发现有出入,则公然拒绝继续进行,并解释向用户表明该站点“存在问题”并为您创建内部支持事件...因为这意味着您存在一个错误,该错误使余额表中的值与交易表有所不同,这表明非常不好的事情™。

Much of this of course is mediated by using database transactions correctly, understanding isolation levels and using locking reads in critical sections. 当然,其中大部分是通过正确使用数据库事务,理解隔离级别以及在关键部分使用锁定读取来进行的。 If the balance table is only modified by a BEFORE INSERT trigger on the transaction table, and not by your code, then there few opportunities for the balance to drift. 如果仅通过事务表上的BEFORE INSERT触发器而不是您的代码来修改余额表,那么余额漂移的机会就很少。

Sanity check are still critical, and in spite of all the hand wringing and arm waving you'll get from people who insist that triggers have excessive performance penalties and should be avoided... they have absolutely no idea what they are talking about. 进行健全性检查仍然很关键,尽管人们总是绞痛和挥舞手臂,但您还是会从那些坚持认为触发器会对性能造成过大惩罚的人那里得到帮助,因此应该避免……他们绝对不知道自己在说什么。 Yes, technically speaking, the mass of the fuel in your car's fuel tank has a negative impact on your fuel economy, but it's a critical part of the operation. 是的,从技术上讲,汽车油箱中的燃料质量对燃油经济性有负面影响,但这是操作的关键部分。 You don't give it any thought. 你什么都不要想。 You don't drive around with a mostly-empty tank. 您不会带着几乎是空的坦克四处行驶。 The cost of firing triggers is negligible, particularly in light of the way they help a database look after itself. 触发触发器的成本可以忽略不计,尤其是考虑到它们帮助数据库照顾自己的方式。 Don't give the cost of triggers a second thought. 不要花时间去思考触发器。

But the scalability concern for a table like this is largely taken care of by proper indexes. 但是,此类表的可伸缩性问题在很大程度上由适当的索引解决。 An index in the transaction table on (account_id, created_on), for example, means the database has a way of instantly locating all of the transactions for one specific account, already sorted by the order in which they occurred, without scanning the table. 例如,交易表中(account_id,created_on)上的索引表示数据库可以立即找到一个特定帐户的所有交易,而无需按照扫描表的顺序进行排序,该交易已经按照发生的顺序进行了排序。 You should find that even among hundreds of millions of rows, finding the transactions for a given account is speedy, with a proper index. 您应该发现,即使在亿万行中,使用适当的索引也可以快速找到给定帐户的交易。

SELECT sum(amount) implies that the transaction amounts are signed, which means the type column is somewhat superfluous, though you might want to keep it and trap it with a trigger requiring credits to be > 0 and debits to be < 0. Sanity checks inside the database, regardless of how seemingly-obvious/unnecessary are, are rarely a bad idea when money is involved. SELECT sum(amount)表示对交易金额进行了签名,这意味着type列有些多余,尽管您可能希望保留它并使用触发条件来捕获它,该触发条件要求贷项> 0且借项<0。在数据库内部,无论看似多么明显/不必要,当涉及到金钱时,它都不是一个坏主意。

You should probably also have BEFORE DELETE and BEFORE UPDATE triggers that deny any changes to the transaction table. 您可能还应该具有BEFORE DELETEBEFORE UPDATE触发器,以拒绝对事务表的任何更改。 Transactions are historical facts that do not change. 交易是不会改变的历史事实。 An incorrect transaction is properly undone by creating a second, offsetting transaction. 通过创建第二个抵消交易可以正确撤消不正确的交易。

A simple example of preventing all deletes on a given table in MySQL Server 5.5 and later looks like thjs: 一个防止在MySQL Server 5.5及更高版本中的给定表上进行所有删除的简单示例看起来像thjs:

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.';

(The usual DELIMITER declarations aren't needed for a trigger with a non-complex body.) (具有非复杂主体的触发器不需要通常的DELIMITER声明。)

I think it's better to have two separate tables for incoming(deposits) and outgoing(withdrawals). 我认为最好有两个单独的表分别用于入金(存款)和出金(提款)。 All positive go to incoming and negative to outgoing, then you assign whatever type you want. 所有正数都输入,负数输出,然后分配所需的任何类型。 However, its an architecture problem, and without knowing your entire platform requirements it's very hard to tell. 但是,这是一个体系结构问题,并且在不了解整个平台要求的情况下很难说出来。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM