你要完成什么并不完全清楚.但是,尽管我理解你的要求,但是没有任何原生的MySQL“锁定”机制可以满足您的需求. (您希望会话能够“锁定”一行以防止其被另一个会话“读取”(或修改).
要完成您尝试执行的操作,这听起来像应用程序问题,而不是数据库完整性问题.
我将用来解决这个问题的方法是向表中添加两列:
locked_by - uniquely identify the session holding the row lock
locked_at - the date/time the row lock was placed
对于尝试获取行锁定的会话,我将检查该行是否已被其他会话锁定,如果不是,则将该行标记为此会话锁定:
UPDATE mytable
SET locked_by = 'me'
, locked_at = NOW()
WHERE unique_row_identifer = someval
AND locked_by IS NULL;
如果更新返回“零行更新”,则表示您没有获得锁定.
如果返回值非零,则表示您获得了一个锁(至少在一行上).
要检查我的会话是否已在该行上持锁:
SELECT 1
FROM mytable t
WHERE t.unique_row_identifier = someval
AND locked_by = 'me';
一旦我知道我在行上有一个“锁定”,那么我可以用一个简单的SELECT来检索它
SELECT ... WHERE unique_row_identifier = someval`
要释放锁定,会话会将locked_by和locked_at列设置回NULL.
“只读”会话可以通过检查locked_by列中的值来避免读取锁定的行:
SELECT t.*
FROM mytable t
WHERE t.unique_row_identifier = someval
AND t.locked_by IS NULL
只有在未锁定的情况下才会返回该行.
请注意,我会在单个语句中执行锁定和检查,以避免出现同时出现的竞争条件.如果我运行SELECT来进行检查,然后进行UPDATE,那么另外一个会话可能会在这两个单独的语句之间滑动……很难真正导致这种情况发生,而不会增加显着的延迟.但是,如果我们要打扰锁定行,我们最好这样做.
请注意,当我们想要检查已经持有很长时间的锁时,存储在locked_at列中的值将起作用.也许会话占用了一些锁,并且该会话已经消失,并且这些锁永远不会被释放.可以安排单独的维护任务来查看表中是否存在非常旧的locked_at值.
或者,您可以使用locked_at为锁执行更复杂的查找,并考虑过期的旧锁.
WHERE ( locked_at IS NULL OR locked_at < (NOW() + INTERVAL 24 HOUR) )
===
注意:
我之前从未在生产系统中使用过这种方法.我的团队通常关心的问题是“胜利中的最后一个”场景,其中更新可能会覆盖最近另一个会话所做的更改.但是我们解决的问题似乎与你想要完成的问题有很大的不同.
要解决“胜利中的最后一个”问题,我们在表中添加一个“版本”列(简单整数).当我们检索一行时,我们检索版本列的当前值.当会话稍后想要更新该行时,它通过将先前检索的版本值与表中的当前值进行比较来验证没有对该行进行其他更新.如果版本号匹配,我们允许更新行,并将版本号增加1. (我们在一个UPDATE语句中完成所有操作,因此操作是原子操作,以避免两个同时会话都不进行更新的竞争条件.我们使用此模式是因为我们真的不想让行被锁定一个会话,永远保持锁定.我们只是阻止同时更新互相覆盖,这再次与你想要完成的声音不同.