12

如何在 MySQL 中停止竞争条件?手头的问题是由一个简单的算法引起的:

  1. 从表中选择一行
  2. 如果不存在,则插入

然后要么你得到一个重复的行,要么你通过唯一/主键阻止它,一个错误。

现在通常我认为事务在这里有帮助,但由于该行不存在,事务实际上并没有帮助(或者我错过了什么?)。

LOCK TABLE 听起来有点矫枉过正,尤其是在表每秒更新多次的情况下。

我能想到的唯一其他解决方案是 GET_LOCK() 用于每个不同的 id,但没有更好的方法吗?这里也没有可扩展性问题吗?而且,对每个表都这样做听起来有点不自然,因为对我来说这听起来像是高并发数据库中非常常见的问题。

4

8 回答 8

10

你想要的是LOCK TABLES

或者如果这看起来过分,那么INSERT IGNORE会检查该行是否已实际插入。

如果使用 IGNORE 关键字,则执行 INSERT 语句时发生的错误将被视为警告。

于 2008-11-05T10:44:24.557 回答
4

在我看来,您的 id 列上应该有一个唯一索引,因此重复插入会触发错误,而不是再次被盲目接受。

这可以通过将 id 定义为主键或单独使用唯一索引来完成。

我认为你需要问的第一个问题是为什么你有很多线程在做同样的工作?为什么他们必须插入完全相同的行?

在得到回答之后,我认为只是忽略错误将是最高效的解决方案,但是测量这两种方法(GET_LOCK v/s 忽略错误)并自己看看。

我知道没有其他方法。为什么要避免错误?当发生另一种类型的错误时,您仍然需要编写代码。

正如 staticsan 所说,事务确实有帮助,但正如它们通常隐含的那样,如果两个插入由不同的线程运行,它们都将在隐含事务中并看到数据库的一致视图。

于 2008-11-05T10:45:23.133 回答
3

锁定整个表确实是矫枉过正。为了获得您想要的效果,您需要一些文献中称为“谓词锁”的东西。除了发表在发表学术研究的论文上之外,没有人见过这些。下一个最好的事情是锁定数据的“访问路径”(在某些 DBMS 中:“页面锁定”)。

一些非 SQL 系统允许您在一个语句中同时执行 (1) 和 (2),这或多或少意味着您的操作系统在 (1) 和 (2) 之间暂停执行线程所产生的潜在竞争条件完全是被淘汰。

尽管如此,在没有谓词锁的情况下,这样的系统仍然需要求助于某种锁定方案,并且它所采用的锁的“粒度”(/“范围”)越精细,并发性就越好。

(总结一下:一些 DBMS——尤其是那些你不需要付费的——确实没有提供比“整个表”更精细的锁定粒度。)

于 2009-11-15T21:40:39.587 回答
2

在技​​术层面上,事务在这里会有所帮助,因为在您提交事务之前,其他线程不会看到新行。

但在实践中,这并不能解决问题——它只会移动它。您的应用程序现在需要检查提交是否失败并决定要做什么。我通常会让它回滚你所做的,并重新启动事务,因为现在该行将可见。这就是基于事务的程序员应该如何工作。

于 2008-11-05T10:47:01.557 回答
0

我遇到了同样的问题并在网上搜索了一会儿:)

最后,我提出了类似于在共享(临时)目录中创建文件系统对象以安全打开临时文件的方法的解决方案:

$exists = $success = false;
do{
 $exists = check();// select a row in the table 
 if (!$exists)
  $success = create_record();
  if ($success){
   $exists = true;
  }else if ($success != ERROR_DUP_ROW){
    log_error("failed to create row not 'coz DUP_ROW!");
    break;
  }else{
    //probably other process has already created the record,
    //so try check again if exists
  }
}while(!$exists)

不要害怕忙循环——通常它会执行一次或两次。

于 2009-08-21T07:31:17.340 回答
0

通过在表上放置唯一索引,您可以非常简单地防止重复行。这与 LOCKS 或 TRANSACTIONS 无关。

您是否关心插入是否因为重复而失败?如果失败需要通知吗?还是插入行才是最重要的,谁或多少重复插入失败并不重要?

如果你不在乎,那么你只需要INSERT IGNORE. 根本不需要考虑事务或表锁。

InnoDB 自动具有行级锁定,但这仅适用于更新和删除。你是对的,它不适用于插入。您无法锁定尚不存在的内容!

您可以显式地LOCK显示整个表。但是,如果您的目的是防止重复,那么您做错了。同样,使用唯一索引。

如果要进行一组更改并且您想要一个全有或全无的结果(或者甚至是一个更大的全有或全无结果中的一组全有或全无结果),则使用事务和保存点。然后使用ROLLBACKorROLLBACK TO SAVEPOINT *savepoint_name*撤消更改,包括删除、更新插入。

LOCK表不是事务的替代品,但它是不支持事务的 MyISAM 表的唯一选择。如果行级锁定还不够,您也可以将它与 InnoDB 表一起使用。有关使用带有锁表语句的事务的更多信息,请参阅此页面

于 2013-01-14T06:18:01.937 回答
0

我有一个类似的问题。我有一张表,在大多数情况下应该有一个唯一的 ticket_id 值,但在某些情况下我会有重复;不是最好的设计,但它就是这样。

  1. 用户 A 检查票是否已预订,但未预订
  2. 用户 B 检查票是否已预订,不是
  3. 用户 B 将“保留”记录插入到该票证的表中
  4. 用户 A 将“保留”记录插入到该票证的表中
  5. 用户 B 检查重复?是的,我的记录更新了吗?是的,离开它
  6. 用户 A 检查重复?是的,我的记录更新了吗?不,删除它

用户 B 已预订票,用户 A 报告该票已被其他人拿走。

在我的例子中,关键是你需要一个决胜局,在我的例子中,它是行上的自动增量 ID。

于 2013-08-16T20:33:16.723 回答
0

如果插入忽略不适合您接受的答案中的建议,那么根据您问题中的要求:

1]从表
2中选择一行]如果它不存在,插入它

另一种可能的方法是在插入 sql 语句中添加条件,例如:

INSERT INTO table_listnames (name, address, tele)
SELECT * FROM (SELECT 'Rupert', 'Somewhere', '022') AS tmp
WHERE NOT EXISTS (
    SELECT name FROM table_listnames WHERE name = 'Rupert'
) LIMIT 1;

参考: https ://stackoverflow.com/a/3164741/179744

于 2019-11-05T13:38:38.590 回答