mysql - 如何正确使用事务和锁来保证数据库的完整性?

标签 mysql database concurrency transactions locking

我开发了一个在线预订系统。为简化起见,假设用户可以预订多个项目,并且每个项目只能预订一次。商品首先添加到购物车中。

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

这是到目前为止我想出的结帐程序:

  1. Begin transaction
  2. Select items in the shopping cart (with for update lock)
    Records from cart-item and items tables are fetched at this step.
  3. Check if items haven't been booked by anybody else
    Basically check if quantity > 0. It's more complicated in the real application, thus I put it here as a separate step.
  4. Update items, set quantity = 0
    Also perform other essential database manipulations.
  5. Make payment (via external api like PayPal or Stripe)
    No user interaction is necessary as payment details can be collected before checkout.
  6. If everything went fine commit transaction or rollback otherwise
  7. Continue with non-essential logic
    Send e-mail etc in case of success, redirect for error.


我不确定这是否足够。我担心是否:
  • 其他用户同时尝试预订相同的项目将被正确处理。他的交易 T2 会等到 T1 完成吗?
  • 使用 PayPal 或 Stripe 付款可能需要一些时间。这不会成为性能方面的问题吗?
  • 物品的可用性将始终正确显示(物品在结帐成功之前应该可用)。这些只读选择是否应该使用 shared lock
  • MySql 是否有可能自己回滚事务?通常是自动重试还是显示错误消息并让用户重试更好?
  • 我想如果我在 SELECT ... FOR UPDATE 表上做 items 就足够了。这样,双击引起的请求和其他用户都必须等到事务完成。他们会等待,因为他们也使用 FOR UPDATE 。同时,vanilla SELECT 只会在事务之前看到 db 的快照,但没有延迟,对吧?
  • 如果我在 JOIN 中使用 SELECT ... FOR UPDATE ,两个表中的记录都会被锁定吗?
  • 我对 SELECT ... FOR UPDATE 在 Willem Renzema 答案的不存在的行部分有点困惑。什么时候会变得重要?你能提供任何例子吗?

  • 以下是我阅读过的一些资源:
    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

    重写我原来的问题,使其更一般。
    添加了后续问题。

    最佳答案

    1. Begin transaction
    2. Select items in shopping cart (with for update lock)


    到目前为止一切顺利,这至少可以防止用户在多个 session 中结帐(多次尝试结帐同一张卡 - 处理双击的好方法。)

    1. Check if items haven't been booked by other user


    你怎么检查?使用标准 SELECT 还是使用 SELECT ... FOR UPDATE ?根据第 5 步,我猜您正在检查项目上的保留列或类似内容。

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

    1. Make payment
    2. Update items marking them as reserved
    3. If everything went fine commit transaction, rollback otherwise


    根据上述内容,根据您提供的信息,如果您没有在第 3 步中使用 SELECT ... FOR UPDATE,您最终可能会有多人购买同一件商品。

    建议的解决方案
  • 开始交易
  • SELECT ... FOR UPDATE cart-item 表。

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

    如果 cart-item 表显示它已被订购,请确保在此处结束结帐过程。
  • SELECT ... FOR UPDATE 记录已预订项目的表格。

  • 这将锁定其他购物车/用户无法阅读这些项目。

    根据结果​​,如果物品没有保留,继续:
  • UPDATE ... 步骤 3 中的表,将项目标记为保留。执行您需要的任何其他 INSERT s 和 UPDATE s。
  • 付款。如果付款服务说付款无效,则发出回滚。
  • 记录付款,如果成功。
  • 提交事务

  • 确保您没有在第 5 步和第 7 步之间做任何可能会失败的事情(例如发送电子邮件),否则您最终可能会导致他们在没有记录的情况下付款,以防交易被回滚。

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

    交易付款与否

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

    但是,在这种情况下,您实际上确实希望他们等待。只是时间长短的问题。

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

    SELECT ... FOR UPDATE 对不存在的行

    只是一个警告,如果您的表设计涉及在您需要更早的位置插入行 SELECT ... FOR UPDATE :如果行不存在,则该事务不会导致其他事务等待,如果它们也 SELECT ... FOR UPDATE 相同的不存在行。

    因此,请确保始终通过在您知道首先存在的行上执行 SELECT ... FOR UPDATE 来序列化您的请求。然后您可以在可能存在或可能不存在的行上 SELECT ... FOR UPDATE。 (不要尝试在可能存在或可能不存在的行上只执行 SELECT,因为您将在事务开始时读取行的状态,而不是在您运行 SELECT 的那一刻。所以, SELECT ... FOR UPDATE在不存在的行上仍然需要做一些事情才能获得最新的信息,请注意它不会导致其他事务等待。)

    关于mysql - 如何正确使用事务和锁来保证数据库的完整性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40749730/

    相关文章:

    java - 同步缓存服务实现

    c# - 如何将 "Parallel.ForEach"作为后台任务执行,将控制立即返回给调用方法?

    php - 如何使用这种特定格式的数据,如笛卡尔坐标? (php)

    php - 对index.php中的非对象调用成员函数fetch()

    mysql - MySQL的隐藏功能

    database - VBA 中的记录集是什么? ... 它的作用是什么?

    php - MYSQL一对多关系插入数组

    mysql - sql语句语法错误?

    sql - 在 SQL 中,当使用带有 'having' 子句的聚合函数时,该函数是否必须在 select 语句中?

    java - 处理长时间运行的 EDT 任务(f.i. TreeModel 搜索)