2016-11-22 81 views
5

我开发了一个在线预订系统。为了简化,我们假设用户可以预订多个项目,并且每个项目只能预订一次。项目首先添加到购物车。如何正确使用事务和锁确保数据库的完整性?

应用程序使用MySql/InnoDB数据库。根据MySql文档,默认隔离级别为Repeatable reads

这里是我和到目前为止想出了检验过程:

  1. BEGIN TRANSACTION
  2. 选择项目在购物车(与for update锁)从
    记录cart-itemitems在这一步取得表格。
  3. 检查是否有物品未被其他人预订
    基本检查是否quantity > 0。在实际应用中它更复杂,因此我把它作为一个单独的步骤放在这里。
  4. 更新项目,设置quantity = 0
    也执行其他基本数据库操作。
  5. 付款(通过外部API如贝宝或条纹)
    没有用户互动是必要的,因为付款的细节可以在结帐前收集。
  6. 如果一切正常提交事务或回滚否则
  7. 与非基本逻辑继续
    发送电子邮件等,在成功的情况下,重定向错误。

我不确定这是否足够。我担心是否:

  1. 其他试图同时预订同一项目的用户将被正确处理。他的交易T2会等到T1完成吗?
  2. 使用PayPal或Stripe付款可能需要一些时间。这不会成为性能方面的问题吗?
  3. 项目可用性将始终显示正确(项目应可用,直到结帐成功)。如果这些只读选择使用shared lock
  4. MySql有可能自行回滚事务吗?自动重试或显示错误消息并让用户再试一次通常会更好吗?
  5. 我想它足够了,如果我在items表上做SELECT ... FOR UPDATE。这样,双击和其他用户引起的请求都必须等到事务结束。他们会等待,因为他们也使用FOR UPDATE。同时香草SELECT只会在交易前看到数据库的快照,尽管如此,对吧?
  6. 如果我在SELECT ... FOR UPDATE中使用JOIN,两个表中的记录是否都会被锁定?
  7. 我有点困惑选择...更新不存在的行部分威廉雷泽马答案。什么时候变得重要?你能提供任何例子吗?

这里有一些资源,我读过: How to deal with concurrent updates in databases?MySQL: Transactions vs Locking TablesDo database transactions prevent race conditions?Isolation (database systems)InnoDB Locking and Transaction ModelA beginner’s guide to database locking and the lost update phenomena

重写了我原来的问题,使之更加普遍。
增加后续问题。

+0

重要的问题:当你'SELECT ... FOR UPDATE',你选择对您知道已经存在,或者针对您打算插入行的行? –

+0

@WillemRenzema我只选择已通过'车,item'透视表 – Paul

回答

1

1.其他试图预订相同的用户项目将同时处理正确。他的交易T2会等到T1完成吗?

是的。虽然活动事务不断创纪录的FOR UPDATE锁,在使用任何锁其他交易报表(SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODEUPDATEDELETE)将暂停,直到无论是主动事务提交或“锁定等待超时”超过。

2.付款使用PayPal或条纹可能需要一些时间。这不会成为性能方面的问题吗?

这会不会是一个问题,因为这正是是必要的。结账交易应该按顺序执行,即。后者结账不应在前完成前开始。

3项的可用性会显示正确所有的时间(项目应可直到结账成功)。如果这些只读选择使用shared lock

Repeatable reads隔离级别可确保在提交事务之前,事务所做的更改不可见。因此物品可用性将正确显示。在实际支付费用之前,没有任何内容会显示为不可用。没有锁是必要的。

SELECT ... LOCK IN SHARE MODE会导致结帐交易等到它结束。这可能会减慢结账,而不会给予任何回报。

4.是否有可能是MySQL本身回滚事务?自动重试或显示错误消息并让用户再试一次通常会更好吗?

这是可能的。当超出“锁定等待超时”或发生死锁时,可能会回滚事务。在这种情况下,自动重试它是个好主意。
缺省情况下,50秒后挂起的语句失败。

5.我想它足够了,如果我在items表上做SELECT ... FOR UPDATE。这样,双击和其他用户引起的请求都必须等到事务结束。他们会等待,因为他们也使用FOR UPDATE。同时香草SELECT只会看到数据库的快照交易之前,无延迟,虽然,对不对?

是的,SELECT ... FOR UPDATEitems表应该够了。
是,这些选择等待,因为FOR UPDATE是排它锁。
是的,简单SELECT只会抓住价值,因为它是交易开始之前,这会立即发生。

6.如果我在SELECT ... FOR UPDATE中使用JOIN,两个表中的记录是否都会被锁定?

是,SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODEUPDATEDELETE锁定所有读取记录,所以无论我们JOIN是包括在内。见MySql Docs

什么(至少对我来说)是在SQL语句的处理扫描一切有趣wheter它被选中或不被锁定,不管。例如WHERE id < 10也会锁定id = 10的记录!

如果没有适合您的语句的索引,并且MySQL必须扫描整个表才能处理该语句,则表的每一行都会被锁定,从而阻止其他用户的所有插入操作到表中。创建好的索引非常重要,以便您的查询不会不必要地扫描多行。

4
  1. BEGIN TRANSACTION
  2. 在购物车中选择项目(与更新锁)

到目前为止好,这至少会阻止用户在多个做结帐会话(多次尝试结帐同一张卡片 - 很好地处理双击)。

  1. 检查项目没有被其他用户

预订您如何检查?与标准SELECT或用SELECT ... FOR UPDATE?基于第5步,我猜你正在检查项目上的保留列,或类似的东西。

的这里的问题是,在步骤2中SELECT ... FOR UPDATE是不会到FOR UPDATE锁适用于一切。它仅适用于SELECT ed:cart-item表。根据名称,这将成为每个购物车/用户的不同记录。这意味着其他交易不会被阻止进行。

  • 进行付款
  • 将它们标记为保留
  • 如果一切正常提交事务更新项目,回滚否则
  • 按照上面的,根据您提供的信息,如果您在步骤3中未使用SELECT ... FOR UPDATE,则最终可能会有多个人购买同一商品。

    建议的解决方案

    1. BEGIN TRANSACTION
    2. SELECT ... FOR UPDATEcart-item表。

    这将锁定双击运行。你在这里选择的应该是某种“购物车订购”栏。如果你这样做,第二个事务将在这里暂停并等待第一个完成,然后读取第一个保存到数据库的结果。

    如果cart-item表格表示已订购,请务必在此处结束结帐流程。

    1. SELECT ... FOR UPDATE该表格记录了一个项目是否已被保留。

    这会锁定其他购物车/用户无法读取这些项目。

    基于该结果,如果项目没有保留,继续:

  • UPDATE ...表在步骤3中,标记的项目作为保留。还需要其他的INSERTUPDATE

  • 付款。如果付款服务表示付款无效,则发出回滚。

  • 记录付款,如果成功。

  • 提交交易

  • 确保你不要做任何可能的步骤5和7(如发送电子邮件)之间的失败,否则你可能最终与他们进行支付的,没有它被记录,在事务回滚的情况下。

    第3步是确保两个(或更多)人不尝试订购相同物品的重要步骤。如果两个人尝试,第二个人最终会在网页“挂起”的同时处理第一个网页。然后当第一个完成时,第二个将读取“保留”列,并且可以向用户返回消息,表明某人已经购买了该项目。

    付款交易与否

    这是主观的。通常,您希望尽快关闭事务处理,以避免多人被锁定与数据库同时进行交互。

    但是,在这种情况下,您确实希望他们等待。这只是一个多久的问题。

    如果您选择在付款前提交交易,则需要在某个中间表中记录您的进度,运行付款并记录结果。请注意,如果付款失败,则必须手动撤消更新的项目预留记录。

    SELECT ... FOR上不存在行

    警告只是一个字,如果你的表的设计涉及到将在您需要提前SELECT ... FOR UPDATE行UPDATE:如果行不存在,则该事务不会导致其他交易等待,如果它们也是SELECT ... FOR UPDATE相同的不存在的行。

    所以,一定要始终在您知道存在第一排做SELECT ... FOR UPDATE系列化你的请求。那么你可以在SELECT ... FOR UPDATE行上存在或不存在。 (不要试图对可能或可能不存在该行做只是SELECT,因为你会在事务开始时被读取行的状态,而不是此刻的你运行SELECT。所以,SELECT ... FOR UPDATE在不存在的行上仍然需要做某些事情才能获取最新的信息,但请注意,它不会导致其他交易等待。)

    +0

    感谢这么详细的回答存在的行! 您做出了很大的问题,我应该避免可能支付之间失败,并提交任何代码。因此,我改变了步骤4和5 我想我会离开内幕交易付款的订单,否则就可能发生这样的'网友认为结账2'被拒绝,但“采取”经过一段时间的项目重新出现,因为'用户1'从未完成付款。我已更新我的帖子以澄清步骤并添加了一些后续问题,请看一下! – Paul

    相关问题