模块课程(3/4)
锁定并选择更新
了解数据库如何在单个终端上独立工作很简单......不等,我已经说过了!
有时“原子更新”(例如 stock = stock - 1)还不够。
假设您必须检查用户是否有足够的余额来购买价值 50 欧元的东西:
1.SELECT balance FROM users WHERE id = 1; 2. JS:如果余额< 50,就屏蔽他们!3. 否则继续结帐... 4.运行推演! (在这里,您会发现一个用户在验证步骤 2 中的支票时,启动了另一个并行的支票,该支票烧毁了他们的帐户,现在您的账户余额已低于零!)。
FOR UPDATE 显式锁定
在 SELECT 末尾添加 FOR UPDATE 会神奇地将您无辜的读取转换为针对其他客户端的密封锁,直到下一次提交。在您决定如何处理这些特定的选定行之前,没有其他人能够对它们进行更新(或并发读取更新)!
BEGIN;
-- If id=3 had already been locked by T1, T2 will sit in perpetual loading on this "read"
-- frozen in place, before receiving the raw data, until T1 runs COMMIT freeing everyone!
SELECT balance
FROM users
WHERE id = 3
FOR UPDATE;
-- at this point our Node JS instance lives with the guarantee, knowing for certain that NO ONE ELSE in the world has
-- manipulated (nor been able to read with intent to alter) that balance while we complete the application logic
UPDATE users SET balance = balance - 50 WHERE id = 3;
COMMIT;甚至还有 FOR SHARE ,它允许其他读取器继续读取(如果位于基本锁上),而不会阻止状态读取,但会阻止任何可能的后续更新,直到您提交为止。但对于修改,请始终使用独占锁定子句的强力:
自己尝试一下
打开正常的时间范围事务并独占锁定产品 ID=5 的读取。\n运行两步查询:\n1.打开事务 (BEGIN)\n2.通过附加“FOR UPDATE”选项,查询“products”获取 ID 5 的完整行,并保证没有人会更改它。
显示提示
BEGIN;,换行符,SELECT * FROM products WHERE id = 5 FOR UPDATE;
3 次尝试后可用的解决方案
有时事务会挂起灾难性的一段时间(死锁或无限等待)。\n为了防止这种情况,您可以命令数据库在该行不立即可用时放弃:在最后使用 NOWAIT,这将导致 SQL Server 异常反弹回 JS 进行尝试捕获,而不会永远挂起。\n对类别 ID=1 运行 SELECT 并使用 FOR UPDATE 但强制 NOWAIT。
显示提示
从类别中选择 *,其中 id = 1 立即更新;
3 次尝试后可用的解决方案