Mysql读取lockingSELECT FOR UPDATE
编辑
我使用node.js felixge-mysql并有一个mysql连接池 。
原版的
我有一个MySQL数据库中有两个表:
- “会话”,存储元数据:用户ID(2),主题,时间戳等
- “消息”存储具有FK与conversation.id的消息
现在我总是这样做:
-
SELECT
一个“对话” - 检查元数据是否允许请求操作
- 对“对话”执行
UPDATE
(更改一些元数据,例如lastUpdatedTimestamp) - 可能会将消息插入“消息”中。
消息旁边的用户也可以block
对话(从他的身边!)
对话UPDATE
和可能的消息INSERT
将发生在一个事务中。
一个警告:在SELECT
对话行并在应用程序级别检查元数据之后,可能是请求的操作不被允许,从而导致UPDATE
和INSERT
从不被执行!
Q1
现在如何读取locking对话行从我select它的那一刻? 但是当元数据导致“用户错误”时,仍然能够释放锁(例如,当前userId不是“this”对话中的userId)。
Q2
现在我正在使用redis'locks'db,它通过使用Lua来locking一个给定的id,并使用node.js事件来释放这个锁。 这些redis锁有一个超时。 (例如1000毫秒)。 有没有办法在MySQL锁上设置超时?
目前还不完全清楚你正在努力完成什么。 但最好我明白你在问什么,没有任何本地的MySQL“锁”机制,将要做你所需要的。 (您希望会话能够“locking”一行以防止其被另一个会话“读取”(或修改)。
要完成你正在做的事情,这听起来像是一个应用程序问题,而不是数据库完整性问题。
我将用来解决这个问题的方法是在表中添加两列:
locked_by - uniquely identify the session holding the row lock locked_at - the date/time the row lock was placed
对于尝试获取行locking的会话,我会检查该行是否已被另一个会话locking,如果不是,则将该行标记为已被此会话locking:
UPDATE mytable SET locked_by = 'me' , locked_at = NOW() WHERE unique_row_identifer = someval AND locked_by IS NULL;
如果更新返回的是“零行更新”,那么您知道您没有获得locking。 如果返回值不为零,则知道您获得了一个锁(至less在一行上)。
要检查我的会话是否已经在该行上持有locking,请执行以下操作:
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列中的值来读取locking的行:
SELECT t.* FROM mytable t WHERE t.unique_row_identifier = someval AND t.locked_by IS NULL
只有在没有locking的情况下才能返回该行。
请注意,我会做一个单一的声明locking和检查,以避免与同时情况的竞争条件。 如果我运行一个SELECT进行检查,然后进行更新,则可能会有另一个会话在这两个单独的语句之间滑动……这将很难真正导致这种情况发生,而不会增加显着的延迟。 但是,如果我们打算locking行,我们最好做对了。
请注意,当我们想要检查长时间保存的锁时,存储在locked_at
列中的值将locked_at
。 也许一个会话拿了一些锁,那会话已经消失了,这些锁永远不会被释放。 可以安排一个单独的维护任务来查看真正的旧的locked_at
值。
或者,您可以使用locked_at
为locking做更复杂的查找,并考虑真正的旧locking过期。
WHERE ( locked_at IS NULL OR locked_at < (NOW() + INTERVAL 24 HOUR) )
===
注意:
以前我从来没有在生产系统中使用过这种方法。 我的团队通常关心的问题是“最后一个胜利”场景,更新可能会覆盖另一个会话最近做出的更改。 但是,我们正在解决的问题似乎与您正在努力完成的问题完全不同。
为了解决“最后一个赢”的问题,我们在表中添加一个“版本”列(简单的整数)。 当我们检索一行时,我们检索版本列的当前值。 当会话稍后想要更新该行时,它会通过将以前检索的版本值与表中的当前值进行比较来validation是否没有对该行进行其他更新。 如果版本号匹配,我们允许更新行,并将版本号加1。 (我们在一个UPDATE语句中这样做,所以这个操作是primefaces操作,以避免两个并发会话不能同时更新的竞争条件。我们使用这个模式,因为我们并不想locking一行一个会话,并永久locking,我们只是防止同时更新覆盖另一个,这是不同于你想要完成的声音。
你正在寻找命名的锁(小心,危险的东西,不要在生产服务器上试验锁)。
看一眼:
-
GET_LOCK(str, timeout)
-
RELEASE_LOCK(str)
-
IS_FREE_LOCK(str)
-
IS_USED_LOCK(str)
A1 :select一个唯一的string来locking并使用GET_LOCK
(比如说, GET_LOCK('conversation_' || [id])
;如果返回1
那么locking是你的,然后调用RELEASE_LOCK
(占所有可能的情况,包括错误)。
A2 : GET_LOCK
的第二个参数是以秒为单位的超时。 如果操作超时GET_LOCK
将返回0
。
从官方文件
GET_LOCK(str,timeout)
尝试使用
timeout
秒数的超时获得一个由stringstr
给出的名称的锁。 如果锁已成功获得,则返回1
如果尝试超时(例如,因为另一个客户端先前已locking名称),则返回0
如果发生错误(例如内存不足或线程使用mysqladmin kill )。 如果你有一个用GET_LOCK()
获得的锁,当你执行RELEASE_LOCK()
, 执行一个新的GET_LOCK()
,或者你的连接终止(正常或exception)时,它被释放2 。 用GET_LOCK()
获得的锁不与事务交互。 也就是说,提交事务不会释放在交易过程中获得的任何锁。该函数可用于实现应用程序locking或模拟logginglocking。 名称在服务器范围内locking3 。 如果一个名字被一个客户端locking,
GET_LOCK()
阻止另一个客户端对同名名字的锁的请求。 这使得同意给定锁名称的客户端能够使用该名称来执行合作的咨询locking。 但要注意的是,它也使不属于协作客户端的客户端能够无意或故意locking名称,从而防止任何合作客户端locking该名称。 降低这种可能性的一种方法是使用特定于数据库或应用程序特定的locking名称。 例如,使用formsdb_name.str
或app_name.str
锁名称。
大胆的是我的:
- 意味着每个连接只能保持一个锁(对于您的用户情况不是问题)
- 意味着一旦closures连接,锁将被释放
- 意味着两个不同的连接(甚至来自同一个池)可能不会一次获得相同的外观。