3

这是我上一个问题的后续(你可以跳过它,因为我在这篇文章中解释了这个问题):
MySQL InnoDB SELECT...LIMIT 1 FOR UPDATE Vs UPDATE ... LIMIT 1

环境:

  • Glassfish 上的 JSF 2.1
  • JPA 2.0 EclipseLink 和 JTA
  • MySQL 5.5 InnoDB 引擎

我有一张桌子:

CREATE TABLE v_ext (
  v_id INT NOT NULL AUTO_INCREMENT,
  product_id INT NOT NULL,
  code VARCHAR(20),
  username VARCHAR(30),
  PRIMARY KEY (v_id)
) ENGINE=InnoDB DEFAULT CHARSET=UTF8;

它填充了 20,000 条这样的记录(product_id所有记录为 54 条,code随机生成且唯一,用户名设置为 NULL):

v_id     product_id    code                  username
-----------------------------------------------------
1        54            '20 alphanumerical'   NULL
...
20,000   54            '20 alphanumerical'   NULL

当用户购买产品 54 时,他从该表中获得一个代码。如果用户多次购买,他每次都会得到一个代码(对用户名没有唯一限制)。因为我正在为一项高强度活动做准备,所以我想确保:

  • 不会发生并发/死锁
  • 性能不受需要的锁定机制的影响

从 SO question(见上面的链接)我发现做这样的查询更快:

START TRANSACTION;
SELECT v_id FROM v_ext WHERE username IS NULL LIMIT 1 FOR UPDATE;
// Use result for next query
UPDATE v_ext SET username=xxx WHERE v_id=...;
COMMIT;

username但是,只有在对列使用索引时,我才发现死锁问题。我认为添加索引将有助于加快一点速度,但它会在大约 19,970 条记录后造成死锁(实际上在这个行数上相当一致)。是否有一个原因?我不明白。谢谢你。

4

2 回答 2

3

从纯理论的角度来看,您似乎没有锁定正确的行(第一个语句中的条件与 update 语句中的条件不同;此外,由于 ,您只锁定了一行LIMIT 1,而稍后您可能会更新更多行)。

试试这个:

START TRANSACTION;
SELECT v_id FROM v_ext WHERE username IS NULL AND v_id=yyy FOR UPDATE;
UPDATE v_ext SET username=xxx WHERE v_id=yyy;
COMMIT;

[编辑]

至于死锁的原因,这是可能的答案(来自手册):

如果您没有适合您的语句的索引,并且 MySQL 必须扫描整个表以处理该语句,则表的每一行都会被锁定 (...)

如果没有索引,该SELECT ... FOR UPDATE语句可能会锁定整个表,而使用索引,它只会锁定一些行。因为您没有在第一条语句中锁定正确的行,所以在第二条语句期间获得了额外的锁。

显然,如果整个表都被锁定(即没有索引),就不会发生死锁。在第二种设置中肯定会发生死锁。

于 2012-12-27T10:15:23.113 回答
1

首先,表的定义是错误的。表中没有 tid 列,所以我怀疑主键是 v_id。
其次,如果您选择更新,则锁定该行。在第一个事务完成之前出现的任何其他选择都将等待该行被清除,因为它将达到完全相同的记录。因此,您将等待这一行。
但是,我非常怀疑这在您的情况下可能是一个真正严重的问题,因为首先,您在那里有用户名,其次您在那里有产品 ID。您极不可能在您最初点击的完全相同的记录上获得大量点击,即使您这样做了,事务也应该运行得非常快。
您必须了解,通过使用事务,您通常会放弃并发性以获得一致的数据。没有办法同时支持数据的一致性和并发性。

于 2012-12-27T14:56:35.830 回答