你如何阻止MySQL中的竞争条件?手头的问题是由一个简单的算法引起的:
然后要么获得重复的行,要么通过唯一/主键阻止它,这是一个错误。
现在我通常觉得交易在这里有帮助,但因为行不存在,交易实际上没有帮助(或者我错过了什么?)。
LOCK TABLE听起来有点过分,特别是如果表每秒更新一次。
我能想到的唯一其他解决方案是GET_LOCK()用于每个不同的id,但是没有更好的方法吗?这里也没有可扩展性问题吗?而且,为每个表执行此操作听起来有点不自然,因为它听起来像是高并发数据库中一个非常常见的问题。
答案 0 :(得分:9)
答案 1 :(得分:4)
在我看来,你的id列上应该有一个唯一索引,因此重复插入会触发错误,而不是再次被盲目接受。
可以通过将id定义为主键或单独使用唯一索引来完成。
我认为你需要问的第一个问题是为什么你有很多线程正在做同样的工作?为什么他们必须插入完全相同的行?
在回答之后,我认为忽略错误将是最高效的解决方案,但是测量两种方法(GET_LOCK v / s忽略错误)并亲自看看。
我不知道其他方式。你为什么要避免错误?当发生其他类型的错误时,您仍然需要对案例进行编码。
正如staticsan所说,事务确实有帮助,但正如它们通常所暗示的那样,如果两个插入由不同的线程运行,它们都将位于隐含的事务中,并且看到数据库的一致视图。
答案 2 :(得分:3)
锁定整个桌子确实有点过分。为了获得你想要的效果,你需要一些文学称之为“谓词锁定”的东西。没有人见过那些除了在纸上印刷的学术研究出版的人。接下来最好的事情是锁定数据的“访问路径”(在某些DBMS中:“页锁”)。
某些非SQL系统允许您在一个语句中同时执行(1)和(2),或多或少意味着您的操作系统暂停执行线程在(1)和(2)之间产生的潜在竞争条件,完全被淘汰。
然而,在没有谓词锁的情况下,这样的系统仍然需要采用某种锁定方案,并且它所采用的锁的“粒度”(/“范围”)越精细,并发性越好。 / p>
(并得出结论:一些DBMS - 特别是那些你不需要支付的 - 确实没有提供比“整个表”更精细的锁粒度。)
答案 3 :(得分:2)
在技术层面上,事务将在这里提供帮助,因为在您提交事务之前,其他线程将不会看到新行。
但是在实践中解决问题 - 只有移动它。您的应用程序现在需要检查提交是否失败并确定要执行的操作。我通常会回滚你所做的事情,并重新启动事务,因为现在该行将可见。这就是基于事务的程序员应该如何工作的方式。
答案 4 :(得分: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)
不要害怕忙碌循环 - 通常它会执行一次或两次。
答案 5 :(得分:0)
通过在表上放置唯一索引,可以非常简单地防止重复行。这与LOCKS或TRANSACTIONS无关。
你是否关心插入是否失败,因为它是重复的?如果失败,您需要收到通知吗?或者插入行是否重要,并且重复插入失败的人数或数量无关紧要?
如果您不在乎,那么您需要的只是INSERT IGNORE
。根本不需要考虑事务或表锁。
InnoDB自动进行行级锁定,但这仅适用于更新和删除。你是对的,它不适用于插入。你无法锁定尚不存在的东西!
您可以明确LOCK
整个表格。但如果你的目的是防止重复,那么你做错了。再次,使用唯一索引。
如果要进行一组更改并且您想要一个全有或全无结果(或者甚至是更大的全有或全无结果中的一组全有或全无结果),那么使用事务和保存点。然后使用ROLLBACK
或ROLLBACK TO SAVEPOINT *savepoint_name*
撤消更改,包括删除,更新和插入。
LOCK
表不是事务的替代品,但它是MyISAM表的唯一选项,它不支持事务。如果行级别锁定不够,您也可以将它与InnoDB表一起使用。有关使用具有锁表语句的事务的更多信息,请参阅this page。
答案 6 :(得分:0)
我有类似的问题。我有一个表,在大多数情况下应该有一个唯一的ticket_id值,但在某些情况下我会有重复;不是最好的设计,但它就是它。
用户B保留了故障单,用户A报告该故障单已由其他人提取。
我的实例中的关键是你需要一个打破平局,在我的情况下,它是行上的自动增量ID。
答案 7 :(得分:0)
万一插入忽略不适合您所接受的答案中的建议,则根据您的问题要求:
1]从表中选择一行
2]如果不存在,请插入
另一种可能的方法是向insert 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;