- 为所有表格使用
ENGINE=InnoDB。
-
使用交易:
BEGIN;
do all the work for a single action
COMMIT;
单个操作的经典示例是从一个帐户中取出资金并将其添加到另一个帐户。删除将包括检查透支,在这种情况下,您将拥有ROLLBACK 而不是COMMIT 的代码。
您获得的锁可确保单个操作的所有内容要么完全完成,要么什么都不做。这甚至适用于系统在BEGIN 和COMMIT 之间崩溃的情况。
没有 begin 和 commit,但 autocommit=ON,每条语句都被 begin 和 commit 隐式包围。那就是先前答案中的UPDATE 示例是“原子的”。但是,如果从一个帐户中扣除的钱需要添加到另一个帐户,如果在UPDATE之后发生崩溃会发生什么?钱不见了。所以,你真的需要
BEGIN;
if not enough funds, ROLLBACK and exit
UPDATE to take money from one account
UPDATE to add that money to another account
INSERT into some log or audit trail to track all transactions
COMMIT;
在每一步后检查 -- ROLLBACK 并对任何意外错误采取规避措施。
如果两个(或更多)动作“同时”发生会怎样?
- 一个等待另一个。
- 出现死锁并对其强制执行 ROLLBACK。
但是,在任何情况下,数据都不会被弄乱。
进一步说明...在某些情况下您需要FOR UPDATE:
BEGIN;
SELECT some stuff from a row FOR UPDATE;
test the stuff, such as account balance
UPDATE that same row;
COMMIT;
FOR UPDATE 对其他线程说:“请不要把手放在这一行上,我可能会更改它;请等我完成。”如果没有FOR UPDATE,另一个线程可能会潜入并耗尽您认为存在的资金的帐户。
评论你的一些想法:
- 对于许多用户及其帐户来说,一个表通常就足够了。它将包含每个帐户的“当前”余额。我提到了一个“日志”;那将是一个单独的表格;它将包含“历史”(而不仅仅是“当前”信息)。
-
FOREIGN KEYs 在本次讨论中大多无关紧要。它们有两个目的:验证另一个表是否有应该存在的行;并隐式创建 INDEX 以加快检查速度。
- 流水线?如果您每秒执行的“事务”不超过 100 次,那么您只需担心
BEGIN..COMMIT 逻辑。
- “同时”和“同时”是误用的术语。两个用户“同时”访问数据库的可能性很小——考虑浏览器延迟、网络延迟、操作系统延迟等。再加上大多数这些步骤迫使活动进入单个文件的事实。网络强制一条消息先于另一条消息到达那里。同时,如果您的“交易”之一需要 0.01 秒,谁在乎“同时”请求是否必须等待它完成。关键是,如果需要,我所描述的将强制“等待”以避免弄乱数据。
话虽如此,仍然可能有一些“同时”——如果事务没有触及相同的行,那么从BEGIN 到COMMIT 所花费的几毫秒可能会重叠。考虑一下几乎同时发生的两笔交易的时间线:
BEGIN; -- A
pull money from Alice -- A
BEGIN; -- B
pull money from Bobby -- B
give Alice's money to Alan -- A
give Bobby's money to Betty --B
COMMIT; --A
COMMIT; --B