解决INSERT竞争条件mysql,避免锁定

时间:2013-03-01 10:25:39

标签: mysql innodb

假设我有几个脚本发送(合法!)电子邮件。每个脚本处理一个较大列表的一部分,它们同时运行。在发送之前,必须检查每个地址以避免两次发送到同一地址。

为此,我创建了一个只有电子邮件地址的简单表(mysql 5.1,innodb)。如果它不在表中,则添加它,然后发送邮件。现在我需要避免竞争条件,其中多个脚本同时测试相同的地址并错误地断定它没有被发送到。我想我可以使用锁,但由于性能原因,我宁愿不这样做。

所以我想知道以下替代方案是否正确:

  • 在地址栏添加唯一索引
  • 只需插入地址,无需选择
  • 进行检查
  • 捕获返回的mysql错误代码:如果是1062,则该地址已存在。

在此设置中,是否还存在竞争条件?我的意思是:两个几乎同时插入地址的脚本是否仍然可以断定邮件尚未发送?或者我应该使用锁吗?

谢谢, 斯泰恩

1 个答案:

答案 0 :(得分:0)

首先,我觉得数据库不是最佳选择。虽然你的较大列表是发送电子邮件(我猜你因为尝试瘫痪而大规模)你必须使用临时表,因为你不希望限制向收件人发送不同的电子邮件以前的邮件。

缓存是维护地址列表或充当共享内存资源的服务器的明显选择。

但是你可以在数据库中完成它,根据我的理解,如果一个电子邮件地址不止一次存在并不是非常重要,因为你正在做的就是检查过去没有发送过一个。如果没有锁定策略,您无法真正控制多个脚本同时发送到同一地址的竞争条件。但是,您可以通过使用索引使其更有效。我不会索引实际地址,而是创建一个新的列,其中包含地址的CRC32哈希值(可以是一个32位无符号整数,只占用4个字节的内存)。使用CRC32方法,由于生日悖论,您还必须检查查询中的电子邮件地址。

例如:

SELECT COUNT(*) FROM email_addresses
WHERE email_address_crc = CRC32(?address)
AND email_address = ?address

有一些有效的东西应该有助于对抗竞争条件,但正如我之前所说的那样,唯一可以保证在发送每封电子邮件时锁定数据库的方法,这样你就可以保持一个确切的清单 - 这很遗憾没有t scale并且意味着发送电子邮件的并行任务可能没有帮助。

根据以下评论进行修改:

正如评论中所指出的,我实际上忘了解决svdr替代锁定解决方案的问题。确实,如果地址存在,包含电子邮件地址(或包含活动ID和地址的复合索引)的唯一索引确实会抛出MySQL异常,从而导致并行脚本发送到同一地址的工作解决方案同时。但是,在脚本“尝试”发送电子邮件之前输入地址时,很难处理由于SMTP错误/网络问题而未发送电子邮件等任何异常,这可能导致收件人未收到电子邮件。另外提供这是一个非常简单的INSERT和SELECT它应该可以捕获MySQL异常,但是如果有更复杂的事情,如在事务中包装命令或使用SELECT FOR UPDATE等,这可能会导致死锁情况。

另外两个注意事项是,出于性能原因需要对电子邮件地址字段进行完全索引,如果使用INNODB,则此限制为767字节 - 假设电子邮件地址的最大有效长度为254(如果长度为+1字节,则为使用VARCHAR)你应该没问题,因为你没有一些巨大的主键。

也应该解决索引性能,并且应该评估CHAR与VCHAR。 CHAR字段上的索引查找通常比等效的VCHAR查找快15%到25% - 固定宽度表大小也可以帮助,具体取决于所使用的表引擎。

总结一下,是的,您的非锁定解决方案可行,但应根据您的具体要求进行仔细测试和评估(我不能评论细节,因为我认为您的真实生活场景比您的SO问题更复杂)。如答案的第一行所述,我仍然认为数据库不是最佳选择,缓存或共享内存空间将更有效,更容易实现。