【问题标题】:Mysql High Concurrency UpdatesMysql 高并发更新
【发布时间】:2019-08-02 05:28:45
【问题描述】:

我有一个mysql表:

CREATE TABLE `coupons` (
    `id` INT NOT NULL AUTO_INCREMENT,
    `code` VARCHAR(255),
    `user_id` INT,
    UNIQUE KEY `code_idx` (`code`)
) ENGINE=InnoDB;

该表由数千/数百万个代码组成,最初user_id 对每个人都是 NULL。 现在我有一个 Web 应用程序,它为同时访问该应用程序的数千名用户分配一个唯一代码。考虑到非常高的流量,我不确定处理此问题的正确方法是什么。 我写的查询是:

UPDATE coupons SET user_id = <some_id> where user_id is NULL limit 1;

应用程序以 1000 个请求/秒的并发运行此查询。

我观察到的是整个表都被锁定了,而且扩展性不好。 我该怎么办? 谢谢。

【问题讨论】:

  • 嗨 Sidhant,我在这里看到这个问题已经有一段时间了,您找到解决方案了吗?你能分享一下吗?提前谢谢你。
  • 嗨,Nathalie,作为一种解决方案,我认为 Mysql 无法针对此用例进行扩展。解决方法是在 Redis 中保存这些优惠券的主键并在那里使用集合。

标签: mysql concurrency


【解决方案1】:

据了解,coupons 已预先填充,null user_id 已更新为非空值。

explain update coupons set user_id = 1 where user_id is null limit 1;

这可能需要架构解决方案,但您可能希望在确保表具有所处理列的索引并且有助于快速更新后查看说明。

向 coupons.user_id 添加索引,例如改变 MySQL 的策略。

create unique index user_id_idx on coupons(user_id);
 explain update coupons set user_id = 1 where user_id is null limit 1;
+----+-------------+---------+------------+-------+---------------+-------------+---------+-------+------+----------+------------------------------+
| id | select_type | table   | partitions | type  | possible_keys | key         | key_len | ref   | rows | filtered | Extra                        |
+----+-------------+---------+------------+-------+---------------+-------------+---------+-------+------+----------+------------------------------+
|  1 | UPDATE      | coupons | NULL       | range | user_id_idx   | user_id_idx | 5       | const |    6 |   100.00 | Using where; Using temporary |
+----+-------------+---------+------------+-------+---------------+-------------+---------+-------+------+----------+------------------------------+
1 row in set (0.01 sec)

因此,您应该与 DBA 合作以确保优化数据库实体。需要权衡取舍。

此外,由于您有一个客户端应用程序,因此您有机会预先获取 null coupons.user_id 并直接在 coupons.id 上进行更新。很想知道您的解决方案。

【讨论】:

    【解决方案2】:

    这个问题可能更适合 DBA(而且我不是 DBA),但我会尝试为您提供一些关于正在发生的事情的想法。

    当您执行更新查询时,InnoDB 实际上并没有锁定整个表。接下来要做的是:它放置一个记录锁,以防止任何其他事务插入、更新或删除coupons.user_id 的值为NULL 的行。

    根据您目前的查询(取决于 user_id 是否为 NULL),您不能有并发,因为您的事务将一个接一个地运行,而不是并行运行。 即使您的coupons.user_id 上的索引也无济于事,因为在锁定 InnoDB 时,如果您没有索引,则会为您创建一个影子索引。结果是一样的。

    所以,如果您想提高吞吐量,我可以想到两种选择:

    1. 在异步模式下将用户分配给优惠券。将所有分配请求放入队列中,然后在后台处理队列。可能不适合您的业务规则。
    2. 减少锁定记录的数量。这里的想法是在执行更新时锁定尽可能少的记录。为此,您可以向表中添加一个或多个 indexed 列,然后在 Update 查询的 WHERE 子句中使用索引。

    列的示例是 product_id 或类别,可能是用户位置(国家/地区、邮编)。 那么您的查询将如下所示:

    更新优惠券 SET user_id = WHERE product_id = user_id is NULL LIMIT 1;

    现在 InnoDB 将只锁定带有product_id = &lt;product_id&gt; 的记录。这样你就会有并发性。

    希望这会有所帮助!

    【讨论】:

    • 感谢添加product_id的建议。实际上,我已经将优惠券绑定到索引的 product_id 上。现在,在同一个 product_id 上,我收到了许多更新。想要一个解决方案来解决这个问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-30
    • 2017-07-29
    • 1970-01-01
    相关资源
    最近更新 更多