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