SELECT ... FOR UPDATE和冲突插入

时间:2012-11-28 21:45:24

标签: sql select

我想插入一行,但是如果发生冲突(例如下面的代码),我希望数据库锁定现有的行,以便我可以记录其内容以进行调试。我正在使用READ_COMMITTED事务隔离。

例如:

CREATE TABLE users(id BIGINT AUTO_INCREMENT, name VARCHAR(30),
  count INT NOT NULL, PRIMARY KEY(id), UNIQUE(name));

Thread 1:
  INSERT INTO users(username, count) VALUES('joe', 1000);
  transaction.commit();

Thread 2:
  // Insert fails due to conflict with above record
  INSERT INTO users(username, count) VALUES('joe', 0);

  // Get the conflicting row and log its properties
  SELECT * FROM users WHERE username = 'joe';

如果冲突的行未锁定,则可以在我检查时修改它。我找到的唯一解决方法是在插入之前调用SELECT id FROM users WHERE username = 'joe' FOR UPDATE如果没有发生冲突,是否可以在没有任何开销的情况下实现此目的?

更新:我要求避免冲突或导致SQLException。我只是要求将冲突的行锁定,以便我可以查找触发冲突的值。是的,我知道冲突的记录包含joe,但我想记录其他所有列。

4 个答案:

答案 0 :(得分:1)

不可能消除UNIQUE列的混淆 使用具有唯一列的INSERT行时。

尝试编写从不必处理SQL异常的SQL 只是浪费了精力,总是最终创建失败的SQL 在某些条件下。

处理实时时无法避免异常处理 多线程多用户数据库服务器,除非你 可以负担锁定表,进行更新,并解锁 桌子(在下面会产生可怕的表现 许多用户负担很重)

UNIQUE CONSTRAINT VIOLATION异常将始终发生在第二个INSERT, 因为你的例子中的两个INSERT可以在时间上广泛分开 (例如按小时,天或周);表或行锁定不会改变这一点。

无论如何,这个问题应该在GUI级别解决 选择可能已被前一个选择的“用户名” 用户,需要为“新”用户提供反馈 “抱歉,该用户名已被其他用户使用”,所以 它不太可能处理UNIQUE VIOLATION异常 可以或应该“避免”。

此外,没有理由SELECT ... FOR UPDATE,因为 您需要做的只是SELECT id WHERE name = newName并查看是否 得到结果idnull; (id == null)=>用户名未使用, 但即使这样,两个用户也可以尝试同时获得“未使用”结果 在同一时间,其中一个INSERT仍然可能失败。

在重复UNIQUE上返回INSERT例外时, 第二个INSERT失败并且未创建该记录, 所以没有“重复”记录可以锁定然后再读取 失败的UNIQUE上会返回INSERT例外。

答案 1 :(得分:0)

您使用的是哪个版本的SQL?我不确定我是否理解你的问题,但我认为你可以在触发器中做到这一点。

在触发器中,您可以查看插入的值(冲突的行)并记录它,然后进行回滚。这意味着当您插入行时,如果没有发生冲突,则不必提交任何内容,并且当发生冲突时,将生成日志并且不插入行。

答案 2 :(得分:0)

不,大多数数据库都不支持这种操作。

你可以做一些创建显式交易的技巧

BEGIN TRANSACTION
IF EXIST(SELECT ...)
  ROLLBACK
INSERT INTO...
COMMIT

但这并不是你想要的。实现您所要求的唯一目标是使用一个更低级别的B-TREE样式库。

答案 3 :(得分:0)

似乎没有一种可行的方法可以做到这一点,并且MVCC有一个强有力的迹象表明,如果没有实质性的影响,就无法实现这一点。

总而言之:你必须知道发生了冲突,但无法100%确定原因(没有线程安全的验证)。