在我参与的应用程序中,我遇到了似乎是竞争条件的影响。情况如下,通常,负责某些繁重的应用程序逻辑的页面遵循以下格式:
从test
中选择并确定是否存在与子句匹配的行
如果匹配的行已经存在,我们在这里终止,否则我们继续应用逻辑
使用与我们的初始选择匹配的值插入test
表。
通常,这样可以正常工作并将操作限制为单次执行。但是,在高负载和用户滥用的情况下,许多请求是有意发送的,MySQL允许运行许多应用程序逻辑实例,绕过select子句的限制。
似乎实际上是这样的:
从测试中选择
从测试中选择
从测试中选择
(所有通过检查)
插入测试中
插入测试中
插入测试
我认为这是出于效率原因而做的,但它在我的应用环境中有严重的后果。我试图使用Get_Lock()和Release_Lock(),但这似乎不足以在高负载下,因为竞争条件似乎仍然存在。由于应用程序逻辑非常繁重且所涉及的所有表都不具有事务功能,因此也无法进行事务处理。
对于熟悉此行为的人,是否可以关闭此类处理,以便MySQL始终按照接收顺序处理查询?还有另一种方法可以使这些查询成为原子吗?任何有关此事的帮助将不胜感激,我找不到有关此行为的详细记录。
答案 0 :(得分:2)
这里的问题是,正如你猜测的那样,你有一种竞争条件。
SELECT
和INSERT
必须是一个原子单位。
您执行此操作的方式是通过交易。您无法安全地创建SELECT
,返回PHP,并假设SELECT
的结果将反映出INSERT
时的数据库状态。
如果精心设计的交易(正确的解决方案)正如您所说的那样 - 我仍然强烈推荐它们 - 您将不得不最终INSERT
原子地检查其假设是否仍然为真(例如通过INSERT IF NOT EXISTS
,存储过程或在应用程序中捕获INSERT
的错误。如果不是,它将中止回到您的PHP代码,这必须启动逻辑。
顺便说一下,MySQL很可能 按照收到的顺序执行请求。通过多个同时连接,可以接收SELECT A
,SELECT B
,INSERT A
,INSERT B
。因此,唯一的“解决方案”是一次只允许一个连接 - 这将会破坏您的可扩展性。
答案 1 :(得分:1)
就个人而言,我会以另一种方式进行检查。
尝试插入行。如果它失败了,那么那里已经有了一行。
以这种方式,您检查或复制并在单个查询中插入新行,从而消除了比赛的可能性。