问题 MySQL InnoDB SELECT ... LIMIT 1 FOR UPDATE Vs UPDATE ... LIMIT 1


我在带有InnoDB引擎的MySQL中有一个表v_ext:
  - id:主键
  - 代码:预生成的代码列表(比如1000个代码是随机生成的)
  - user_id:最初为NULL

当用户购买商品时,他们会收到代码。我需要更新表以填充user_id列。我有两个选择:

START TRANSACTION;
SELECT id FROM v_ext WHERE user_id IS NULL LIMIT 1 FOR UPDATE; -- return id 54 for ex.
UPDATE v_ext SET user_id=xxx WHERE id=54;
COMMIT;

要么

UPDATE v_ext SET user_id=xxx WHERE user_id IS NULL LIMIT 1;

如果我有数千名用户同时购买,第二种选择是否安全?如果是这样,假设第二个选项对性能更好是否正确,因为它只需要一个查询?


12341
2017-12-26 11:18


起源

有没有 UNIQUE 约束 user_id? (即用户只能拥有一个代码)? - eggyal
EXISTS 在并发性和性能方面会更好用。 - bonCodigo
UNIQUE不是约束,user_id可以获得与购买项目一样多的代码 - JScoobyCed
@bonCodigo你能详细说说吗?有意思的 UPDATE v_ext SET user_id=xxx WHERE EXISTS (SELECT * FROM v_ext WHERE user_id IS NULL LIMIT 1) ? - JScoobyCed
@JScoobyCed UPDATE v_ext SET user_id=xxx WHERE EXISTS (SELECT * FROM v_ext WHERE ID = 54 AND user_id IS NULL) 但是我相信这里有更多经验丰富的专家可以为你提供一个更好的答案来比较哪种情况最适合你的情况:) +1关于性能的有趣问题。 - bonCodigo


答案:


由于我没有得到答案,我开始做基准测试。我的标准如下:

  • 20,000个预生成的代码
  • 使用Apache ab 命令有20,000个请求,100个并发: ab -n 20000 -c 100
  • Servlet - > EJB(JPA 2.0 EclipseLink,JTA)在DB中执行更新(因为它将通过实际情况下的JSF操作)
  • 2个版本的Servlet,一个带选项1(SELECT ... FOR UPDATE),另一个带选项2(UPDATE ... LIMIT 1)
  • 停止Glassfish,手动点击测试的Servlet 5次以加热它,将所有重置为NULL到user_id
  • 测试每次运行3次,并提供平均值

结果:

SELECT ... FOR UPDATE;更新......:

Concurrency Level:      100
Time taken for tests:   758.116 seconds
Complete requests:      20000
Failed requests:        0
Write errors:           0
Row updated:            20000

更新....限制1:

Concurrency Level:      100
Time taken for tests:   773.659 seconds
Complete requests:      20000
Failed requests:        0
Write errors:           0
Row updated:            20000

所以至少在我的系统上,带有2个查询的选项似乎比一个查询更有效。我没想到:)


13
2017-12-27 06:00



+1有趣的发现。它是否导致任何并发问题? - Ja͢ck
不是我能看到的。我通过20,000个成功请求更新了20,000行。 - JScoobyCed
只有当我将user_id索引为varchar列时才会出现死锁问题 stackoverflow.com/questions/14052038/... - JScoobyCed