mariadb 如何防止在下面的情况下陷入僵局?

2w2cym1i  于 2022-11-08  发布在  其他
关注(0)|答案(1)|浏览(141)

我有一个表,它应该保存OrderGroups的行。基本上,当一个客户创建一个Order时,他的Order应该根据他的客户ID放在一个组中,直到管理员可以验证顺序。OrderGroups表的结构如下:

OrderGroupId                | IsClosed | clientId
-------------------------------------------------
INT PRIMARY (AutoIncrement) | BOOLEAN  | INT

我的程式码应该以下列方式运作:当客户创建一个新订单时,我们应该检查他是否已经有一个未关闭的订单组。2如果他有,我们应该将该订单组附加到他的订单中。3如果他没有,我们应该为他创建一个新的订单组,并将他的订单附加到新创建的组中。
过去,在获取/创建订单组时没有使用锁定,这自然会导致一些客户在同时插入多个订单时,最终会有多个打开的订单组。

BEGIN TRANSACTION;
SELECT * FROM OrderGroups WHERE clientId = {id} AND IsClosed = 0 FOR UPDATE;
// if no order groups are returned, insert a new group and use that one
// if an order group is returned, use the returned order group
END TRANSACTION;

这可以防止出现多个OrderGroup,但有时会导致死锁。我认为这是因为MariaDB在行不存在时锁定行的方式。基本上,如果查询返回结果,则请求同一行的所有后续调用都应该等待,直到第一个请求该行进行更新的事务提交或回滚。但如果一个不存在的行以这种方式被锁定,情况就不是这样了。插入仍然被阻止(这就是为什么我得到死锁),但选择查询被处理。
基本上,这就是发生的事情:

C1 -> BEGIN TRANSACTION;
C1 -> SELECT OrderGroups WHERE clientId = 1 AND IsClosed = 0 FOR UPDATE; // returns no rows
C2 -> BEGIN TRANSACTION;
C2 -> SELECT OrderGroups WHERE clientId = 1 AND IsClosed = 0 FOR UPDATE; // returns no rows, instead of waiting for C1 to commit or rollback the transaction
C1 -> INSERT INTO OrderGroups SET clientId = 1, IsClosed = 0; // holds, because C2 has a for update lock on the row? being inserted
C2 -> INSERT INTO OrderGroups SET clientId = 1, IsClosed = 0; // holds, because C1 has a for update lock on the row
MARIADB -> randomly kills C1 or C2 because of the deadlock, while the other may finish

如何避免这种死锁情况,同时仍然维护OrderGroups表的单个开放组策略?

3okqufwl

3okqufwl1#

目前,我设法用一种黑客的方式解决了这个问题,在表中插入了一个新的、唯一的列。
我在数据库中插入了UniqId (VARCHAR(20))列作为唯一列。当我试图在表中读取或创建新行时,如果没有找到任何内容,我只需使用c{clientId}UniqId列创建一个INSERT IGNORE INTO ...,而不是选择然后插入。每当订单组关闭时,我都会将UniqId列更新为表的主键。
这样,在执行INSERT IGNORE时,如果该行已经存在,它将被锁定,我将能够在下一个SELECT语句中选择它;如果该行还不存在,它将被插入并锁定,我将能够在下一个SELECT语句中选择它。
这是一个相当笨拙的方法来解决我的确切情况,但我仍然愿意接受一些建议,以某种方式为表中的一行获得一个排它锁,而这还不存在。

相关问题