我正在练习一些“系统设计”编码问题,并且对如何解决MySQL中的并发问题感兴趣。问题是“设计库存检查系统”。
假设您正在尝试从清单中检出特定项目,例如图书馆图书。
如果两个人都在网站上想要预订,是否有可能两个人都将其签出?假设查询正在更新行的状态以将布尔checked_out
标记为True
。
交易可以解决这个问题吗?它将导致第二个查询运行失败(假设它们是同一查询)。
或者,我们将行插入checkouts
表中。由于这两个查询都读取到当前未检出该项目,因此它们都可以插入表中。我认为交易不会解决此问题,除非该交易包括读取表格以查看是否存在尚未结束的该项目的结帐。
建议的方法之一
我该如何在完全相同的时间模拟两次写入以测试这一点?
答案 0 :(得分:1)
否,仅交易本身无法解决并发问题。让我们快速回顾mysql's definition of transactions:
事务是可以提交或回滚的原子工作单元。当事务对数据库进行多次更改时,要么在提交事务后所有更改都成功,要么在事务回滚时撤消所有更改。
总而言之:事务是确保数据完整性的一种方式。
RDBMS使用各种类型的locking,isolation levels和storage engine level solutions来解决并发问题。人们经常将交易误认为是控制并发的一种手段,因为交易会影响某些锁的持有时间。
关注InnoDB:当您发出update
语句时,mysql在正在更新的记录上放置一个exclusive lock。只有持有排他锁的事务才能修改给定的记录,其他事务必须等到事务提交后。
这如何帮助您防止多个用户签出同一本书?假设您有一个id
字段,用于唯一标识书籍,还有一个checked_out
字段,用于指示书籍的状态。
您可以使用以下原子update
来检出一本书:
update books set checked_out=1 where id=xxx and checked_out=0
checked_out=0
标准可确保update
仅在尚未结帐的情况下成功。因此,如果以上语句影响一行,那么当前用户将签出该书。如果它不影响任何行,则说明其他人已经签出了这本书。排他锁确保在任何给定时间只有一个事务可以更新记录,从而序列化对该记录的访问。
如果您要使用单独的checkouts
表来预订书籍,则可以在书籍ID上使用唯一索引,以防止同一本书被多次签出。
答案 1 :(得分:0)
事务不会导致更新失败。它们导致查询序列被序列化。只有一个访问者可以运行查询序列。其他人等等。
SQL中的所有内容都是事务,包括单语句更新操作。 BEGIN TRANSACTION; ... COMMIT;
表示的交易类型将一系列查询捆绑在一起。
除非交易成功,否则我认为交易不会解决此问题 包括阅读表格以查看当前是否存在针对 这个项目。
通常是正确的。结帐方案必须始终从数据库中读取可用性。交易的目的是避免在多个用户尝试检出同一项目时出现竞争情况。
SQL没有像多线程处理器内核那样具有线程安全的原子测试和设置指令。因此,您需要使用事务处理这种事情。
最简单的结帐方式是使用交易,类似这样。
BEGIN TRANSACTION;
SELECT is_item_available, id FROM item WHERE catalog_number = whatever FOR UPDATE;
/* if the item is not available, tell the user and commit the transaction without update*/
UPDATE item SET is_item_available = 0 WHERE id = itemIdPreviouslySelected;
/* tell the user the checkout succeeded. */
COMMIT;
两个或多个用户尝试同时或多或少地检出同一项目很可能。但实际上只有其中之一能得到该物品。
更复杂的结帐方案(此处未详细介绍)使用两步系统。第一步:为用户保留商品的交易,如果其他人已经签出或保留商品,则拒绝该商品。第二步:预订持有人有固定的时间接受预订并签出商品,否则预订过期,其他用户可能会预订该商品。