【问题标题】:Mysql read lock SELECT FOR UPDATEMysql读锁SELECT FOR UPDATE
【发布时间】:2014-03-10 07:44:03
【问题描述】:

编辑

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

原创

我有一个 mysql 数据库,其中有 2 个表:

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

现在我总是这样做:

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

在消息旁边,用户还可以block 对话(来自他的身边!)

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

一个警告:在我SELECT 对话行并在应用程序级别检查元数据之后,可能不允许请求的操作,导致UPDATE 和可能的INSERT 永远不会被执行!

第一季度

现在如何从我选择对话行的那一刻起读取锁定对话行?但是当元数据导致“用户错误”时仍然能够释放锁定(例如,当前 userId 不是“this”对话中的 userId)。

第二季度

现在我正在使用一个 redis 'locks' db,它通过使用 Lua 锁定给定的 id 并使用 node.js 事件来释放这个锁。这些 redis 锁有超时。 (例如 1000 毫秒)。有没有办法在 mysql 锁上设置超时?

【问题讨论】:

  • SELECT FOR UPDATE 不会阻止读取访问(简单的 SELECT),但会阻止其他 SELECT FOR UPDATE 读取锁定的行。如果一行被另一个会话锁定,SELECT FOR UPDATE 将保持等待直到锁定被释放。

标签: mysql node.js


【解决方案1】:

您正在寻找命名锁(小心、危险的东西,不要在生产服务器上尝试使用锁:D)。

看看:

A1:选择一个唯一的字符串来锁定并在其上使用GET_LOCK(例如,GET_LOCK('conversation_' || [id]);如果它返回1,则锁定是你的。做任何你想做的事,以后再做致电RELEASE_LOCK(考虑所有可能的情况,包括错误)。

A2GET_LOCK的第二个参数是超时时间。如果操作超时GET_LOCK将返回0

来自官方文档

GET_LOCK(str,timeout)

尝试使用由字符串 str 给出的名称获取锁,使用 timeout 秒的超时。如果获得锁则返回1 成功,0 如果尝试超时(例如,因为另一个 客户端先前已锁定名称),或 NULL 如果发生错误 (例如内存不足或线程被杀死) mysqladmin 杀死)。如果你有一个用GET_LOCK() 获得的锁,它是 当你执行RELEASE_LOCK()时释放,执行一个新的GET_LOCK()1,或者 您的连接终止(正常或异常)2。锁具 使用GET_LOCK() 获得的不与事务交互。那是, 提交事务不会释放任何此类获得的锁 在交易过程中。

此函数可用于实现应用程序锁或 模拟记录锁。 名称在服务器范围内被锁定3。如果一个 名称已被一个客户端锁定,GET_LOCK() 阻止任何请求 另一个客户端的同名锁。这使客户 同意给定的锁名称以使用该名称执行合作 咨询锁定。但请注意,它还可以启用客户端 也不属于锁定名称的合作客户端 无意或故意,从而阻止任何合作 客户锁定该名称。一种降低可能性的方法 这是使用特定于数据库的锁名称或 特定于应用程序。例如,使用形式的锁名称 db_name.strapp_name.str

粗体是我的:

  1. 意味着每个连接只能持有一个锁(对于您的用户案例来说不是问题)
  2. 意味着一旦你关闭连接
  3. 锁就会被释放
  4. 意味着两个不同的连接(即使来自同一个池)可能不会同时获得相同的外观。

【讨论】:

  • 这行不通。我有一个 node.js 应用程序,它有一个连接池。根据这篇文章techblog.procurios.nl/k/n618/news/view/41405/14863/… GET_LOCK() 是每个连接。因此,当使用连接池时,这是行不通的。我将编辑我的问题以明确我使用 mysql 连接池。谢谢你的回答。越来越近了!
  • 它有效,但您误读了文本。 GET_LOCK 目前每个连接只能持有 1 个锁(自 2003 年以来有一个 bug for it 打开),但锁是服务器范围的(否则它们将无用)。我将引用文档更新答案。只要您记得释放锁,连接池就可以了。
  • 好吧,我的错。但是连接被重用,因此一个连接可以同时为多个用户提供服务。由于在应用程序级别有一个短暂的时刻,因此可以为多个用户建立到GET_LOCK() 的相同连接。意味着同时在一个连接中使用多个锁。通过评论变得更聪明!
  • 刚刚阅读了错误报告,多么糟糕的是它仍然没有实现。它会非常有用! bugs.mysql.com/bug.php?id=67806
  • 关于您的第二条评论,我不确定我是否理解您的意思,但如果我理解正确,您认为相同的活动连接可以在两个线程 AB。在A 释放连接之前,B 应该永远无法获得与A 相同的连接(否则来自AB 的事务将混合)。只有在您完成连接后才会将连接放回池中(完成后您将正确释放锁)。
【解决方案2】:

您要完成的工作并不完全清楚。但据我所知,您的要求是,没有任何本机 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_bylocked_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) )

===

注意:

我以前从未在生产系统中使用过这种方法。我的团队通常关心的问题是“最后一个获胜”场景,其中更新可能会覆盖另一个会话最近所做的更改。但是我们正在解决的问题似乎与您要完成的问题大不相同。

为了解决“最后一个获胜”问题,我们在表中添加了一个“版本”列(简单整数)。当我们检索一行时,我们检索版本列的当前值。当会话稍后想要更新该行时,它通过将先前检索到的版本值与表中的当前值进行比较来验证没有对该行进行其他更新。如果版本号匹配,我们允许更新行,并将版本号加一。 (我们在单个 UPDATE 语句中完成所有这些操作,因此操作是原子的,以避免两个同时会话不都进行更新的竞争条件。我们使用这种模式是因为我们真的不想让一行被锁定一个会话,并且永远持有锁。我们只是防止同时更新相互覆盖,这又与您尝试完成的听起来不同。

【讨论】:

  • 感谢这个长长的回答,它可以满足我的需求。因此,我会接受这个作为答案。尽管如此,我还不知道我是否会走这条路,因为我现在正在用一个非常快的 redis db(只有 Lua)做同样的事情。我只是想知道在mysql中是否有更好的方法来做到这一点。但是由于 redis > mysql 在速度方面是一个艰难的决定,还是不做!
  • @aremie:我为我的答案的长度道歉:较短的答案(如果我理解你想要做什么)是我知道 MySQL 数据库中没有任何机制的......除了在整个表上采取排他锁,并且不想去那里,因为它会消除并发性。
  • 一个长答案是好的,因为它“切中要害”。它不会比需要的更长!不用道歉!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-21
  • 1970-01-01
  • 1970-01-01
  • 2020-11-24
  • 2016-03-30
相关资源
最近更新 更多