大家好,祝大家新年快乐! :) 我正在开发一个软件(Java,后端),它有3个组件,每个组件都是独立的Java应用程序(你甚至可以将它们视为单独的线程): 1)DB数据导入器1。 2)DB数据导入器2。 3)DB数据导出器。
现在,所有这些应用程序都在使用相同的MySql数据库,使用相同的2个InnoDB表 - “Orders”和“Items”。每个订单可能包含0个或多个项目。所有数据库操作(数据查询和数据插入)都是使用存储过程完成的。这就是我的应用程序所做的:
应用程序1或2(记住,它们是独立的)每隔几秒开始导入“一个订单”。 “订单”被导入“订单”表,每个“项目”被导入“项目”表。 a)应用程序接受并订购,将该订单导入“订单”表并将订单标记为“未准备好导出”。 b)然后,应用程序继续导入该订单的所有项目(如果有)。 c)最后,当所有项目都被导入时,订单被标记为“准备出口”(我有一个专用的列)。
应用程序“3”每隔几秒执行一次: a)检查“准备出口”订单。 b)选择50“准备出口”订单并将其标记为“出口”。 c)将订单及其物品出口到文件中。 d)将所有“在出口中”的订单标记为“已导出”。
现在,正如您所知,即使使用专用列来说明哪个订单或项目应该是导出器,哪个仍在导入且尚未导出,我会遇到一些死锁和竞争条件。
您是否知道我可以使用任何简单而安全的机制来实现这个“生产者 - 消费者”系统,而无需锁定整个表“订单”和“项目”(因为它们被软件的其他部分主动使用)? 我猜使用3状态列并不是一个好主意,考虑到每个表可能有数百万行,而这样的3状态列上的索引是无效的。必须有更好的东西:)
答案 0 :(得分:1)
您的处理工作流程包括以下步骤:
应用程序“3”每隔几秒执行一次:a)检查“准备好了 导出“订单.b)选择50”准备出口“订单并标记它们 作为“出口”。 c)将订单及其物品出口到文件中。 d) 将所有“在导出中”的订单标记为“已导出”。
重要的是如何执行此步骤。
如果您执行类似此过程的操作,则应该干净地运行。
在我看来,50只是一次批量太大了。我先从一个接一个地做,然后尝试使批量更大。
首先,在单个查询中将某些订单标记为“导出”。这样您就不必担心交易。
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
)并将其标记为“准备出口”。然后它逐一咀嚼它们。
现在,为了避免订单表上的死锁,其余软件中的查询需要包含WHERE status <> 'exporting'
或等效项,因此他们将忽略导出的行。
你可能会在Items表上遇到死锁。您可以通过始终执行像这样的单一查询操作来避免这些
UPDATE items SET number_on_hand = number_on_hand - ???order_quantity???
您也可以查看MySQL的UPSERT版本。它被称为INSERT ... ON DUPLICATE KEY UPDATE.
但是,最重要的是:如果你有一个繁忙的系统,你将不得不使用交易。避免死锁的经典方法是始终以相同的顺序锁定资源。例如,
始终先锁定Order表行,或先锁定Item表行,但不要以相反的顺序。
如果必须在一个表中锁定多行,请始终以相同的顺序锁定它们 - 也就是说,在ORDER BY id
查询中使用SELECT ... FOR UPDATE
子句。
请注意,(status,id)
上订单表上的索引将有助于优化查询以将订单分批并从批次中释放。