MySql 的消费者-生产者模式

consumer-producer pattern with MySql

大家好,新年快乐! :) 我正在开发一个软件(Java,后端),它有 3 个组件,每个组件都是独立的 Java 应用程序(您甚至可以将它们视为单独的线程): 1) 数据库数据导入器 1. 2) 数据库数据导入器 2. 3) 数据库数据导出器。

现在,所有这些应用程序都使用相同的 MySql 数据库,使用相同的 2 InnoDB tables - "Orders" 和 "Items"。每个订单可能有 0 个或多个项目。所有的数据库操作(数据查询和数据插入)都是使用存储过程完成的。这是我的应用程序的作用:

应用程序 1 或 2(请记住,它们是独立的)每隔几秒开始导入 "an order"。 "Order" 正在导入 "Orders" table,每个 "Item" 正在导入 "Items" table。 a) 应用程序获取订单,将该订单导入 "Orders" table 并将订单标记为 "not ready for export"。 b) 然后应用程序继续为该订单导入所有项目(如果有)。 c)最后,当所有项目都被导入时,订单被标记为"ready for export"(我有一个专门的列用于该位)。

应用程序“3”每隔几秒执行一次: a) 检查 "ready for export" 个订单。 b) 选择 50 "ready for export" 个订单并将它们标记为 "in export"。 c) 将订单和他们的项目导出到一个文件中。 d) 标记所有 "in export" 到 "exported".

的订单

现在,如您所知,即使有专门的列告诉哪些订单或项目应该出口,哪些仍在进口但不应该出口,我还是遇到了一些僵局和竞争条件。

你知道我可以用什么简单安全的机制来实现这个 "producer - consumer" 系统而不锁定整个 tables "Orders" 和 "Items"(因为它们是主动的被软件的其他部分使用)? 我猜想使用三态列不是一个好主意,因为每个 table 可能有数百万行,并且在这种三态列上的索引是无效的。必须有更好的东西:)

您的处理工作流程包括以下步骤:

Application "3" every few seconds does this: a) checks for "ready for export" orders. b) selects 50 "ready for export" orders and marks them as "in export". c) exports orders and theirs items into a file. d) marks all orders that are "in export" to "exported".

这一步很重要。

如果您执行类似此过程的操作,它应该 运行 干净利落。

在我看来,一次做 50 个太大了。我会先一个一个地做,然后试着把批次做大一点。

首先,在单个查询中将一些订单标记为 'in export' 这样您就不必担心交易。

UPDATE orders 
   SET status = 'exporting'
 WHERE status = 'ready-for-export'
 ORDER BY id
 LIMIT 50  

然后,在循环中执行此操作以处理匹配中的所有订单

 /* get an order to process */
 SELECT id, whatever, whatever 
   FROM orders
   WHERE status = 'exporting'
   ORDER BY id
   LIMIT 1

 /* if no order came back from this query, you are done with your batch */

 /* process the order */

 /* mark the order done */
 UPDATE orders SET status = 'exported' WHERE id = ???id??? AND status='exporting'

这会抓取一批订单(使用 LIMIT 50)并标记它们 "ready-for-export." 然后它会一个一个地咀嚼它们。

现在,为了避免在订单 table 上出现死锁,软件其余部分中的查询需要包含 WHERE status <> 'exporting' 或等效项,因此它们将忽略正在导出的行。

您可能在项目 table 上遇到了死锁。你可以通过总是像这样single-query操作

来避免这些
 UPDATE items SET number_on_hand = number_on_hand - ???order_quantity???

您也可以查看 MySQL 的 UPSERT 版本。它被称为 INSERT ... ON DUPLICATE KEY UPDATE.

但是,底线是:如果您的系统很忙,您将不得不使用事务。避免死锁的经典方法是始终以相同的顺序锁定资源。例如,

  1. 始终首先锁定订单 table 行,或首先锁定项目 table 行,但绝不能以相反的顺序。

  2. 如果您必须将几行锁定在一个 table 中,请始终以相同的顺序锁定它们——也就是说,在 [=17] 中使用 ORDER BY id 子句=]查询。

请注意,(status,id) 上订单 table 的索引将有助于优化查询以将订单放入批次中并从批次中释放它们。