在 MySQL 中同时分配资源
Assign resources concurrently in MySQL
我正在尝试实施一个市场来销售某些可替代物品,我希望将这些物品伪随机分配给每个用户。
假设我有一个项目集合 items = [1, 2, 3, 4, 5, 6, 7, 8, 9]
.
用户可以请求购买一定数量的物品。 POST /buy?count=N
在我的代码中,我获得了商品样本,我想将这些商品出售给用户 [1, 3, 4] = sample(items)
我目前正在做的是以下内容
- 创建数据库事务
- 从剩余的物品中获取样品以出售给用户(如果 none 可用则失败)
- 通过在 table
user_items
中为每个项目创建条目 (user_id, item_id)
将示例分配给用户(如果我无法分配当前示例,请使用新示例重试)
- 扣除用户购买余额(如果他们负担不起则回滚交易)
- 提交交易
我基本上是使用数据库(mysql)来保证操作的一致性。问题是当我对这个逻辑进行压力测试时,查询失败并显示 ER_LOCK_DEADLOCK
或 ER_LOCK_WAIT_TIMEOUT
.
我尝试了一些事情:
我尝试对示例中的项目进行排序,认为这样可以防止死锁,但没有奏效。
本来想把这些操作都放到一个队列里,然后一个个处理,但是应用在aws上运行一个集群,所以有多个实例。
我认为将数据库隔离级别设置为 SERIALIZABLE
会降低性能,但确实可以解决问题,但事实并非如此。
我认为使用数据库来保证一致性是可行的方法,但我不知道如何。
这里是4个client的简单日志,我明白为什么client 2, 3, 4无法锁定资源(5和8被client 1锁定)但是为什么会出现僵局?为什么他们不能失败?
C1 AVAILABLE 9 [1, 2, 3, 4, 5, 6, 7, 8, 9] PURCHASING 3 [ 1, 5, 8 ]
C2 AVAILABLE 9 [1, 2, 3, 4, 5, 6, 7, 8, 9] PURCHASING 3 [ 5, 7, 9 ]
C3 AVAILABLE 9 [1, 2, 3, 4, 5, 6, 7, 8, 9] PURCHASING 3 [ 4, 5, 9 ]
C4 AVAILABLE 9 [1, 2, 3, 4, 5, 6, 7, 8, 9] PURCHASING 3 [ 2, 4, 8 ]
C2 FAILED for 3 [ 5, 7, 9 ] ER_LOCK_DEADLOCK: insert into `user_items` (`item_id`, `user_id`) values (5, 6) - ER_LOCK_DEADLOCK: Deadlock found when trying to get lock; try restarting transaction
C4 FAILED for 3 [ 2, 4, 8 ] ER_LOCK_DEADLOCK: insert into `user_items` (`item_id`, `user_id`) values (2, 6) - ER_LOCK_DEADLOCK: Deadlock found when trying to get lock; try restarting transaction
C3 FAILED for 3 [ 4, 5, 9 ] ER_LOCK_DEADLOCK: insert into `user_items` (`item_id`, `user_id`) values (4, 6) - ER_LOCK_DEADLOCK: Deadlock found when trying to get lock; try restarting transaction
C1 ASSIGNED SUCCESSFULLY 3 [ 1, 5, 8 ]
问题出在间隙锁上,我在其中执行插入的 table 在两列上都有唯一索引。为了防止重复并通常保持索引更新,数据库引擎锁定跨越多个值的整个区域。
发生的事情是并发查询将不同区域锁定在彼此之上,导致死锁。
我通过使用更新来获取项目的行锁来更改逻辑,如果更新成功,我可以继续插入,否则我只是中止事务。
通过这种方式,我要么在任何给定时间有一个查询 运行(如果分区导致将某些值分配给不同的查询,则只有一个会继续)或对不相交的数据集进行多个查询。
我正在尝试实施一个市场来销售某些可替代物品,我希望将这些物品伪随机分配给每个用户。
假设我有一个项目集合 items = [1, 2, 3, 4, 5, 6, 7, 8, 9]
.
用户可以请求购买一定数量的物品。 POST /buy?count=N
在我的代码中,我获得了商品样本,我想将这些商品出售给用户 [1, 3, 4] = sample(items)
我目前正在做的是以下内容
- 创建数据库事务
- 从剩余的物品中获取样品以出售给用户(如果 none 可用则失败)
- 通过在 table
user_items
中为每个项目创建条目(user_id, item_id)
将示例分配给用户(如果我无法分配当前示例,请使用新示例重试) - 扣除用户购买余额(如果他们负担不起则回滚交易)
- 提交交易
我基本上是使用数据库(mysql)来保证操作的一致性。问题是当我对这个逻辑进行压力测试时,查询失败并显示 ER_LOCK_DEADLOCK
或 ER_LOCK_WAIT_TIMEOUT
.
我尝试了一些事情:
我尝试对示例中的项目进行排序,认为这样可以防止死锁,但没有奏效。
本来想把这些操作都放到一个队列里,然后一个个处理,但是应用在aws上运行一个集群,所以有多个实例。
我认为将数据库隔离级别设置为 SERIALIZABLE
会降低性能,但确实可以解决问题,但事实并非如此。
我认为使用数据库来保证一致性是可行的方法,但我不知道如何。
这里是4个client的简单日志,我明白为什么client 2, 3, 4无法锁定资源(5和8被client 1锁定)但是为什么会出现僵局?为什么他们不能失败?
C1 AVAILABLE 9 [1, 2, 3, 4, 5, 6, 7, 8, 9] PURCHASING 3 [ 1, 5, 8 ]
C2 AVAILABLE 9 [1, 2, 3, 4, 5, 6, 7, 8, 9] PURCHASING 3 [ 5, 7, 9 ]
C3 AVAILABLE 9 [1, 2, 3, 4, 5, 6, 7, 8, 9] PURCHASING 3 [ 4, 5, 9 ]
C4 AVAILABLE 9 [1, 2, 3, 4, 5, 6, 7, 8, 9] PURCHASING 3 [ 2, 4, 8 ]
C2 FAILED for 3 [ 5, 7, 9 ] ER_LOCK_DEADLOCK: insert into `user_items` (`item_id`, `user_id`) values (5, 6) - ER_LOCK_DEADLOCK: Deadlock found when trying to get lock; try restarting transaction
C4 FAILED for 3 [ 2, 4, 8 ] ER_LOCK_DEADLOCK: insert into `user_items` (`item_id`, `user_id`) values (2, 6) - ER_LOCK_DEADLOCK: Deadlock found when trying to get lock; try restarting transaction
C3 FAILED for 3 [ 4, 5, 9 ] ER_LOCK_DEADLOCK: insert into `user_items` (`item_id`, `user_id`) values (4, 6) - ER_LOCK_DEADLOCK: Deadlock found when trying to get lock; try restarting transaction
C1 ASSIGNED SUCCESSFULLY 3 [ 1, 5, 8 ]
问题出在间隙锁上,我在其中执行插入的 table 在两列上都有唯一索引。为了防止重复并通常保持索引更新,数据库引擎锁定跨越多个值的整个区域。
发生的事情是并发查询将不同区域锁定在彼此之上,导致死锁。
我通过使用更新来获取项目的行锁来更改逻辑,如果更新成功,我可以继续插入,否则我只是中止事务。
通过这种方式,我要么在任何给定时间有一个查询 运行(如果分区导致将某些值分配给不同的查询,则只有一个会继续)或对不相交的数据集进行多个查询。