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