2

在 MySQL 中,我必须检查 select 查询是否返回了任何记录,如果没有,我插入一条记录。我担心 PHP 脚本中的整个 if-else 操作并不像我希望的那样原子,即在某些情况下会中断,例如,如果在需要处理相同记录的情况下调用脚本的另一个实例:

if(select returns at least one record)
{
    update record;
}
else
{
    insert record;
}

我在这里没有使用事务,并且自动提交是打开的。我正在使用 MySQL 5.1 和 PHP 5.3。该表是 InnoDB。我想知道上面的代码是否次优并且确实会中断。我的意思是相同的脚本被两个实例重新输入,并且发生以下查询序列:

  1. 实例1尝试选择记录,没有找到,进入block进行insert查询
  2. 实例2尝试选择记录,没有找到,进入block进行insert查询
  3. 实例 1 尝试插入记录,成功
  4. 实例 2 尝试插入记录,失败,自动中止脚本

这意味着实例 2 将中止并返回错误,跳过插入查询语句之后的任何内容。我可以使错误不是致命的,但我不喜欢忽略错误,我更愿意知道我的恐惧是否在这里真实存在。

更新:我最终做了什么(这样可以吗?)

有问题的表有助于限制(实际上是允许/拒绝)应用程序发送给每个收件人的消息数量。系统不应在 Z 期间内向收件人 Y 发送超过 X 条消息。该表 [概念上] 如下:

create table throttle
(
    recipient_id integer unsigned unique not null,
    send_count integer unsigned not null default 1,
    period_ts timestamp default current_timestamp,
    primary key (recipient_id)
) engine=InnoDB;

还有 [有点简化/概念性的] PHP 代码块,它应该执行原子事务,维护表中的正确数据,并根据油门状态允许/拒绝发送消息:

function send_message_throttled($recipient_id) /// The 'Y' variable
{
    query('begin');

    query("select send_count, unix_timestamp(period_ts) from throttle where recipient_id = $recipient_id for update");

    $r = query_result_row();

    if($r)
    {
        if(time() >= $r[1] + 60 * 60 * 24) /// The numeric offset is the length of the period, the 'Z' variable
        {/// new period
            query("update throttle set send_count = 1, period_ts = current_timestamp where recipient_id = $recipient_id");
        }
        else
        {
            if($r[0] < 5) /// Amount of messages allowed per period, the 'X' variable
            {
                query("update throttle set send_count = send_count + 1 where recipient_id = $recipient_id");
            }
            else
            {
                trigger_error('Will not send message, throttled down.', E_USER_WARNING);
                query('rollback');
                return 1;
            }
        }
    }
    else
    {
        query("insert into throttle(recipient_id) values($recipient_id)");
    }

    if(failed(send_message($recipient_id)))
    {
        query('rollback');
        return 2;
    }

    query('commit');
}

好吧,不管发生 InnoDB 死锁的事实,这很好,不是吗?我没有捶胸顿足之类的,但这只是我能做到的性能/稳定性的最佳组合,没有使用 MyISAM 并锁定整个表,我不想这样做,因为更频繁的更新/插入 vs选择。

4

2 回答 2

3

似乎您已经知道问题的答案,以及如何解决您的问题。这是一个真正的问题,您可以使用以下方法之一来解决它:

  • 选择 ... 更新
  • 插入 ... 在重复密钥更新时
  • 交易(不要使用 MyIsam)
  • 表锁
于 2010-06-23T18:24:15.613 回答
1

可以而且可能会发生,具体取决于此页面的执行频率。

安全的赌注是使用交易。你写的同样的事情仍然会发生,除了你可以安全地检查事务中的错误(如果插入涉及多个查询,并且只有最后一个插入中断)允许你回滚变得无效的那个。

所以(伪):

#start transaction
if (select returns at least one record)
{
    update record;
}
else
{
    insert record;
}

if (no constraint errors)
{
    commit; //ends transaction
}
else
{
    rollback; //ends transaction
}

您也可以锁定表,但根据您所做的工作,您必须在整个表上获得排他锁(您不能不存在SELECT ... FOR UPDATE不存在的行,抱歉),但这也会阻止从您的表中读取,直到你完蛋了。

于 2010-06-23T18:23:22.177 回答