Mysql读取lockingSELECT FOR UPDATE

编辑

我使用node.js felixge-mysql并有一个mysql连接

原版的

我有一个MySQL数据库中有两个表:

  1. “会话”,存储元数据:用户ID(2),主题,时间戳等
  2. “消息”存储具有FK与conversation.id的消息

现在我总是这样做:

  1. SELECT一个“对话”
  2. 检查元数据是否允许请求操作
  3. 对“对话”执行UPDATE (更改一些元数据,例如lastUpdatedTimestamp)
  4. 可能会将消息插入“消息”中。

消息旁边的用户也可以block对话(从他的身边!)

对话UPDATE和可能的消息INSERT将发生在一个事务中。

一个警告:在SELECT对话行并在应用程序级别检查元数据之后,可能是请求的操作不被允许,从而导致UPDATEINSERT从不被执行!

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_bylocked_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 (占所有可能的情况,包括错误)。

A2GET_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.strapp_name.str锁名称。

大胆的是我的:

  1. 意味着每个连接只能保持一个锁(对于您的用户情况不是问题)
  2. 意味着一旦closures连接,锁将被释放
  3. 意味着两个不同的连接(甚至来自同一个池)可能不会一次获得相同的外观。