我开发了一个在线预订系统。为了简化,假设用户可以预订多个项目,每个项目只能预订一次。物品首先被添加到购物车。
应用使用MySql
/ InnoDB
数据库。根据MySql文档,默认隔离级别为Repeatable reads
。
这是我到目前为止提出的结帐程序:
- 开始交易
- 选择购物车中的商品(
for update
锁定)
此步骤将提取cart-item
和items
表中的记录。- 检查项目是否未被其他人预订
基本上检查是否quantity > 0
。它在实际应用中更复杂,因此我把它作为一个单独的步骤。- 更新项,设置
quantity = 0
还要执行其他重要的数据库操作。- 付款(通过外部API,如PayPal或Stripe)
无需用户互动,因为可以在结账前收集付款详细信息。- 如果一切顺利提交事务或回滚,否则
- 继续使用非必要逻辑
醇>
如果成功,请发送电子邮件等,重定向错误。
我不确定这是否足够。我担心是否:
T2
会等到T1
完成吗?shared lock
?SELECT ... FOR UPDATE
表上items
,我认为已经足够了。这样,双击和其他用户引起的请求都必须等到事务完成。他们会等待,因为他们也使用FOR UPDATE
。与此同时,vanilla SELECT
只会在事务发生前看到db的快照,但没有延迟,对吗?JOIN
中使用SELECT ... FOR UPDATE
,两个表中的记录都会被锁定吗?以下是我读过的一些资源: How to deal with concurrent updates in databases?,MySQL: Transactions vs Locking Tables,Do database transactions prevent race conditions?, Isolation (database systems),InnoDB Locking and Transaction Model,A beginner’s guide to database locking and the lost update phenomena。
重写了我原来的问题,使其更加通用。
添加了后续问题。
答案 0 :(得分:4)
- 开始交易
- 选择购物车中的商品(使用更新锁定)
醇>
到目前为止,这至少会阻止用户在多个会话中进行结账(多次尝试签出同一张卡 - 很好地处理双击。)
- 检查其他用户是否未预订项目
醇>
你如何检查?使用标准SELECT
或SELECT ... FOR UPDATE
?基于第5步,我猜你正在检查项目上的保留列,或类似的东西。
这里的问题是第2步中的SELECT ... FOR UPDATE
不会将FOR UPDATE
锁应用于其他所有内容。它仅适用于SELECT
ed:cart-item
表。根据名称,这将是每个购物车/用户的不同记录。这意味着其他交易不会被阻止继续进行。
- 付款
- 更新将其标记为已保留的项目
- 如果一切顺利,则提交事务,否则回滚
醇>
根据您提供的信息,如果您未在步骤3中使用SELECT ... FOR UPDATE
,则最终可能会有多人购买相同的商品。
SELECT ... FOR UPDATE
cart-item
表。这将锁定从运行中双击。你在这里选择的应该是某种“购物车订购”栏目。如果这样做,第二个事务将在此处暂停并等待第一个事务完成,然后读取第一个保存到数据库的结果。
如果cart-item
表显示已经订购,请务必在此结束结帐流程。
SELECT ... FOR UPDATE
您记录项目是否已被保留的表格。这将锁定其他购物车/用户无法阅读这些商品。
根据结果,如果未保留项目,请继续:
UPDATE ...
步骤3中的表格,将项目标记为已保留。您还需要其他INSERT
和UPDATE
。
付款。如果付款服务显示付款无效,则发出回滚。
记录付款,如果成功。
提交交易
确保您不会在步骤5和7之间执行任何可能失败的操作(例如发送电子邮件),否则如果事务被回滚,您可能最终会在没有记录的情况下进行付款。< / p>
步骤3是确保两个(或更多)人不尝试订购相同物品的重要步骤。如果有两个人尝试,第二个人最终会在处理第一个网页时“挂起”。然后,当第一个完成时,第二个将读取“保留”列,并且您可以向用户返回某人已经购买该项目的消息。
这是主观的。通常,您希望尽快关闭事务,以避免多个人被锁定,无法立即与数据库交互。
然而,在这种情况下,你确实希望他们等待。这只是一个问题。
如果您选择在付款前提交交易,则需要在某个中间表中记录您的进度,运行付款,然后记录结果。请注意,如果付款失败,您必须手动撤消更新的商品预订记录。
如果你的表设计涉及在早先需要SELECT ... FOR UPDATE
之前插入行,请注意警告:如果一行不存在,那么该事务不会导致其他事务等待,如果它们也{ {1}}同一个不存在的行。
因此,请确保始终在您知道存在的行上执行SELECT ... FOR UPDATE
来序列化您的请求。然后,您可以SELECT ... FOR UPDATE
在可能存在或可能不存在的行上。 (不要试图在可能存在或不存在的行上只有SELECT ... FOR UPDATE
,因为您将在事务开始时读取行的状态,而不是在您运行{时{1}}。因此,对于不存在的行,SELECT
仍然是您需要做的事情,以便获取最新信息,只要知道它不会导致其他事务等待。)
答案 1 :(得分:2)
<强> 1。尝试同时预订同一项目的其他用户将被正确处理。他的交易T2
会等到T1
完成吗?
是。当活动事务对记录保持FOR UPDATE
锁定时,使用任何锁定(SELECT ... FOR UPDATE
,SELECT ... LOCK IN SHARE MODE
,UPDATE
,DELETE
)的其他事务中的语句将被暂停直到活动事务提交或&#34;锁定等待超时&#34;超过了。
<强> 2。使用PayPal或Stripe付款可能需要一些时间。这不会成为性能方面的问题吗?
这不是问题,因为这正是必要的。结帐交易应按顺序执行,即。后面的结账不应该在前完成之前开始。
第3。项目可用性将始终正确显示(项目应该可用,直到结帐成功)。这些只读选项是否应使用shared lock
?
Repeatable reads
隔离级别确保在提交事务之前,事务所做的更改不可见。因此,项目可用性将正确显示。在实际支付之前,什么都不会显示。不需要锁。
SELECT ... LOCK IN SHARE MODE
会导致结帐事务等待完成。这可能会减慢结账速度而不给予任何回报。
<强> 4。 MySql可能会自行回滚事务吗?通常最好自动重试或显示错误消息并让用户再试一次?
有可能。当&#34;锁定等待超时&#34;时,可以回滚交易。超过或发生死锁时。在这种情况下,自动重试是个好主意 默认情况下,挂起的语句在50秒后失败。
<强> 5。如果我在SELECT ... FOR UPDATE
表上items
,我想它已经足够了。这样,双击和其他用户引起的请求都必须等到事务完成。他们会等待因为他们也使用FOR UPDATE
。与此同时,vanilla SELECT
只会在交易前看到db的快照,但没有延迟,对吧?
是的,SELECT ... FOR UPDATE
表上的items
就足够了
是的,这些选择等待,因为FOR UPDATE
是一个独占锁
是的,简单的SELECT
只会获取交易开始前的价值,这将立即发生。
<强> 6。如果我在JOIN
中使用SELECT ... FOR UPDATE
,那么两个表中的记录都会被锁定吗?
是的,SELECT ... FOR UPDATE
,SELECT ... LOCK IN SHARE MODE
,UPDATE
,DELETE
会锁定所有阅读记录,因此无论我们JOIN
是什么。请参阅MySql Docs。
有趣的是(至少对我来说)在SQL语句处理过程中扫描的所有内容都会被锁定,无论它是否被选中。例如,WHERE id < 10
也会使用id = 10
锁定记录!
如果没有适合您的语句的索引,并且MySQL必须扫描整个表来处理该语句,则表的每一行都会被锁定,这反过来会阻止其他用户对表的所有插入。创建好的索引非常重要,这样您的查询就不会不必要地扫描很多行。