[英]How do I lock on an InnoDB row that doesn't exist yet?
如何保证我可以搜索数据库中是否存在用户名,然后将该用户名作为新行插入数据库中,而不会在SELECT
和INSERT
语句之间进行任何拦截?
几乎就像我锁定了一个不存在的行。 我想用用户名"Foo"锁定不存在的行,以便我现在可以检查它是否存在于数据库中,如果它不存在,则将其插入到数据库中而不会中断。
我知道使用LOCK IN SHARE MODE
和FOR 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.