繁体   English   中英

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

[英]How do I lock on an InnoDB row that doesn't exist yet?

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

几乎就像我锁定了一个不存在的行。 我想用用户名"Foo"锁定不存在的行,以便我现在可以检查它是否存在于数据库中,如果它不存在,则将其插入到数据库中而不会中断。

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

虽然上面的答案是正确的,因为 SELECT ... FOR UPDATE 将阻止并发会话/事务插入相同的记录,但这不是全部事实。 我目前正在与同样的问题作斗争,并且得出的结论是 SELECT ... FOR UPDATE 在这种情况下几乎没用,原因如下:

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

换句话说, SELECT ... FOR UPDATE 阻止其他会话插入相应的记录,但即使您执行 SELECT ... FOR UPDATE 并且未找到相应的记录,您也可能实际上无法插入那个记录。 恕我直言,这使“第一个查询,然后插入”方法无用。

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

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

只是我的 2 美分...

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

如果没有找到合适的索引(满足WHERE条件),那么高效的记录锁定是不可能的,整个表都会被锁定*。

此锁将一直保持到发出SELECT ... FOR UPDATE的事务结束。

这些手册页中可以找到有关此主题的一些非常有趣的信息。

*我说高效,因为实际上记录锁实际上是对索引记录的锁 当找不到合适的索引时,只能使用默认的聚集索引,并且会被全锁。

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

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

你在“正常化”吗? 也就是说,该表是 id 和名称对的列表? 并且您正在插入一个新的“名称”(并且可能想要在其他表中使用该id )?

然后有UNIQUE(name)并做

INSERT IGNORE INTO tbl (name) VALUES ($name);

这并没有解释如何使用刚刚创建的id ,但您没有询问。

请注意,在发现是否需要之前分配了“新” id 因此,这可能会导致AUTO_INCREMENT值快速增加。

也可以看看

 INSERT ... ON DUPLICATE KEY UPDATE ...

以及与VALUES()LAST_INSERT_ID(id)一起使用的技巧。 但是,同样,您没有在问题中说明真正的目的,所以我不想不必要地深入探讨更多细节。

注意:以上不关心autocommit的值是什么,或者语句是否在显式事务中。

为了一次标准化一批“名称”,这里给出的 2 个 SQL 非常有效: http : //mysql.rjweb.org/doc.php/staging_table#normalization并且该技术避免了“刻录”ID 并避免任何运行时错误。

不直接回答问题,但使用 Serializable 隔离级别不能实现最终目标吗? 假设最终目标是避免重复名称。 冬宫

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM