我想插入一行,但是如果发生冲突(例如下面的代码),我希望数据库锁定现有的行,以便我可以记录其内容以进行调试。我正在使用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
,但我想记录其他所有列。
答案 0 :(得分:1)
不可能消除UNIQUE
列的混淆
使用具有唯一列的INSERT
行时。
尝试编写从不必处理SQL异常的SQL 只是浪费了精力,总是最终创建失败的SQL 在某些条件下。
处理实时时无法避免异常处理 多线程多用户数据库服务器,除非你 可以负担锁定表,进行更新,并解锁 桌子(在下面会产生可怕的表现 许多用户负担很重)
UNIQUE CONSTRAINT VIOLATION
异常将始终发生在第二个INSERT
,
因为你的例子中的两个INSERT
可以在时间上广泛分开
(例如按小时,天或周);表或行锁定不会改变这一点。
无论如何,这个问题应该在GUI级别解决
选择可能已被前一个选择的“用户名”
用户,需要为“新”用户提供反馈
“抱歉,该用户名已被其他用户使用”,所以
它不太可能处理UNIQUE VIOLATION
异常
可以或应该“避免”。
此外,没有理由SELECT ... FOR UPDATE
,因为
您需要做的只是SELECT id WHERE name = newName
并查看是否
得到结果id
或null
; (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%确定原因(没有线程安全的验证)。