如何锁定尚不存在的InnoDB行?

时间:2013-06-12 14:55:40

标签: mysql locking innodb rowlocking database-locking

如何保证我可以搜索我的数据库中是否存在用户名,然后将该用户名作为新行插入数据库,而SELECTINSERT语句之间没有任何拦截?

几乎就像我锁定了一个不存在的行。我想用用户名“Foo”锁定不存在的行,这样我现在可以检查它是否存在于数据库中并将其插入到数据库中(如果它不存在的话)任何中断。

我知道使用LOCK IN SHARE MODEFOR UPDATE存在,但据我所知,这只适用于已经存在的行。我不知道在这种情况下该怎么做。

5 个答案:

答案 0 :(得分:17)

如果username上有索引(应该是这种情况,如果不是,请添加一个,最好是UNIQUE),然后发出SELECT * FROM user_table WHERE username = 'foo' FOR UPDATE;将阻止任何并发创建此用户的事务(以及非唯一索引时的“上一个”和“下一个”可能值)。

如果找不到合适的索引(以满足WHERE条件),则无法进行有效的记录锁定,整个表将被锁定*。

此锁定将一直持续到发出SELECT ... FOR UPDATE

的交易结束

有关此主题的一些非常有趣的信息可以在these manual pages中找到。

* 我说效率很高,因为事实上a record lock is actually a lock on index records。如果找不到合适的索引,则只能使用默认的clustered index,并且它将被完全锁定。

答案 1 :(得分:13)

虽然上面的答案是正确的,因为SELECT ... FOR UPDATE会阻止并发会话/事务插入相同的记录,这不是完整的事实。我目前正在解决同样的问题,并得出结论,SELECT ... FOR UPDATE在这种情况下几乎无用,原因如下:

并发事务/会话也可以在相同的记录/索引值上执行SELECT ... FOR UPDATE,并且MySQL会很乐意立即接受(非阻塞)并且不会抛出错误。当然,一旦其他会话完成,您的会话也不能再插入记录了。您和其他会话/交易也不会获得有关情况的任何信息,并认为他们可以安全地插入记录,直到他们真正尝试这样做。尝试插入然后导致死锁或重复键错误,具体取决于具体情况。

换句话说,SELECT ... FOR UPDATE会阻止其他会话插入相应的记录,但即使您执行SELECT ... FOR UPDATE并且找不到相应的记录,也可能是& #39;实际上是插入那条记录。恕我直言,提出"第一个查询,然后插入"方法没用。

问题的原因是MySQL没有提供任何方法来真正锁定不存在的记录。两个并发会话/事务可以锁定不存在的记录" FOR UPDATE"与此同时,这是一件真正不可能的事情,这使得发展变得更加困难。

解决这个问题的唯一方法似乎是使用信号量表或在插入时锁定整个表。有关锁定整个表或使用信号量表的更多参考,请参阅MySQL文档。

只是我的2美分......

答案 2 :(得分:7)

锁定不存在的记录在MySQL中不起作用。有几个关于它的错误报告:

一种解决方法是使用mutex table,其中在插入新记录之前将锁定现有记录。例如,有两个表:卖家和产品。卖家有很多产品,但不应该有任何重复的产品。在这种情况下,卖家表可以用作互斥表。在插入新产品之前,将在卖方记录上创建锁定。使用此附加查询,可以保证在任何给定时间只有一个线程可以执行操作。没有重复。没有死锁。

答案 3 :(得分:0)

没有直接回答问题,但是使用Serializable隔离级别是否可以实现最终目标?假设最终目标是避免名称重复。来自Hermitage

  

MySQL“可序列化”可防止反依赖周期(G2):

set session transaction isolation level serializable; begin; -- T1
set session transaction isolation level serializable; begin; -- T2
select * from test where value % 3 = 0; -- T1
select * from test where value % 3 = 0; -- T2
insert into test (id, value) values(3, 30); -- T1, BLOCKS
insert into test (id, value) values(4, 42); -- T2, prints "ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction"
commit;   -- T1
rollback; -- T2

答案 4 :(得分:0)

您正在“正常化”?也就是说,该表是一对ID和名称对的列表?并且您要插入一个新的“名称”(并且可能希望将if !person.Equals(body.Person) { // Trigger API update } 用于其他表中?)?

然后有id

UNIQUE(name)

这不能解释如何创建刚刚创建的INSERT IGNORE INTO tbl (name) VALUES ($name); ,但是您没有对此询问。

请注意,在发现是否需要分配“ newt” id之前,先进行分配。因此,这可能导致id值迅速增加。

另请参见

AUTO_INCREMENT

以及与 INSERT ... ON DUPLICATE KEY UPDATE ... VALUES()结合使用的技巧。但是,同样,您没有在问题中说明 real 的目的,所以我不想不必要地进一步介绍细节。

注意:上面的内容并不关心LAST_INSERT_ID(id)的值是什么,也不管该语句是否在显式事务内。

要一次全部标准化一批“名称”,此处给出的2条SQL非常有效:http://mysql.rjweb.org/doc.php/staging_table#normalization,该技术避免了“燃烧” id并避免了任何运行时错误。