2008-11-05 80 views
10

如何在MySQL中停止竞争条件?眼前的问题是由一个简单的算法造成的:mysql插入竞争条件

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

,然后要么你得到一个重复行,或如果您通过唯一/主键防止出现错误。

现在通常我会觉得这里的交易有所帮助,但由于该行不存在,该交易实际上不利于(或我思念的东西?)。

LOCK TABLE听起来像是一种矫枉过正,尤其是如果表格每秒更新多次。

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

回答

9

你想要的是LOCK TABLES

,或者如果这似乎过度该行实际上是插入怎么样INSERT IGNORE用支票。

如果使用忽略关键字,在执行INSERT语句 发生的错误 被视为警告 代替。

+0

使用INSERT IGNORE进行操作。触发器会导致太多开销,除非业务逻辑复杂,否则它们是不值得的。 – captainspi 2013-06-08 21:30:03

+0

只是`INSERT`有什么问题,并且有唯一的约束和捕捉错误来说“好吧,记录已经存在”。 – KeatsKelleher 2014-05-22 14:03:11

4

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

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

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

之后被回答,我认为只是忽略的错误将是最高效的解决方案,但同时测量方法(GET_LOCK V/S忽略错误),并看到自己。

我没有其他方式知道。为什么你想避免错误?当发生另一种类型的错误时,您仍然需要编写该案例。

由于staticsan说交易做帮助,但,因为它们通常是隐含的,如果两个刀片通过不同的线程运行,他们都将是一个隐含的交易中,看到了数据库的一致意见。

+0

嗯,当然,我们有独特的想法等等......事实上,这就是我们意识到存在的问题是唯一索引触发的错误。 – tpk 2008-11-05 10:59:35

+2

这就是它应该如何工作,你准备交易失败......在这种情况下,它似乎很容易:如果重复键错误,忽略,因为该行已经存在。全表/行锁可能更多的是性能问题,而不仅仅是在发生错误时忽略错误。衡量虽然 – 2008-11-05 11:14:48

2

在技术层面上,交易将帮助这里,因为其他线程不会看到新行,直到您提交事务。

但在实践中不解决问题 - 它只是移动它。您的应用程序现在需要检查提交失败并决定要执行的操作。我通常会让它回滚你所做的事情,然后重新启动事务,因为现在该行会显示。这是基于事务的程序员应该如何工作的。

0

我遇到了同样的问题,并搜查了净了一下:)

最后我想出了类似的方法解决creating filesystem objects in shared (temporary) directories to securely open temporary files:

$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) 

不要害怕busy-的循环 - 通常它会执行一次或两次。

3

锁定整个表确实是矫枉过正。为了达到你想要的效果,你需要一些文献中称之为“谓词锁定”的东西。没有人见过那些除了在学术研究发表的论文上印刷的人。其次,锁定数据的“访问路径”(在某些DBMS中:“页锁”)。

一些非SQL系统允许您在一个语句中同时执行(1)和(2),这或多或少意味着您的操作系统在(1)和(2)之间挂起执行线程)完全消除。然而,在没有谓词锁的情况下,这样的系统仍然需要求助于某种锁定方案,并且所需锁的“粒度”(/“范围”)越精细,并发性越好。

(并得出结论:某些DBMS的 - 特别是那些你不必支付 - 确确实实提供了比“整个表”没有更细锁的粒度。)

0

您防止重复行非常简单地通过把独特的索引放在你的桌子上。这与LOCKS或TRANSACTIONS无关。

如果插入失败是因为它是重复的吗?如果失败,你需要通知吗?或者,插入该行是否重要,并且插入失败的人数或插入次数无关紧要。

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

InnoDB自动进行行级锁定,但仅适用于更新和删除操作。你说得对,它不适用于插入。你不能锁定尚不存在的东西!

您可以明确LOCK整个表。但如果你的目的是为了防止重复,那么你做错了。再次使用唯一的索引。

如果存在一组要更改并且您希望得到全有或全无的结果(或者甚至是在更大的全有或全无结果内的一组或全部或全部结果),则使用事务和保存点。然后使用ROLLBACKROLLBACK TO SAVEPOINT *savepoint_name*撤销更改,包括删除,更新插入。

LOCK表不是交易的替代品,但它是MyISAM表的唯一选项,它不支持交易。如果行级别锁定不够,您还可以将它与InnoDB表一起使用。有关在锁表语句中使用事务的更多信息,请参阅this page

0

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

  1. 用户A进行检查以查看如果票证被保留,这是不
  2. 用户B进行检查以查看如果票证被保留,这是不
  3. 用户B插入一个“保留”记录进入该票的表格
  4. 用户A向该票证的表中插入“保留”记录
  5. 用户B检查重复吗?是的,我的记录是否更新?是的,离开它
  6. 用户检查重复?是的,我的记录是否更新?不,删除它

用户B已预订票证,用户A报告该票据已被其他人占用。

在我的例子中的关键是,你需要一个打破平局,在我的情况下,它是行上的自动递增ID。