You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免WordPress抽奖插件函数在多实例下重复执行产生多名中奖者

根因分析

这是典型的读写竞态条件问题:多个请求进程在高负载下几乎同时执行getLastDiceDay查询,此时还没有任何进程完成当日中奖记录写入,所有进程都通过了校验,后续各自执行抽奖、写入逻辑,最终出现多中奖者的异常。共享主机的IO延迟会放大查询和写入的时间差,导致问题更容易触发,低负载的测试环境自然难以复现。

文件标识方案评估

该方案理论上可以解决问题,但存在较多隐患:

  • 共享主机的文件系统权限不稳定,容易出现锁文件写入失败、异常崩溃后锁文件无法删除的问题,会导致后续抽奖流程完全卡住
  • 需要额外开发锁过期、异常兜底逻辑,增加代码复杂度
  • 如果站点后续扩展为多节点部署,文件锁跨节点不生效
更优解决方案

推荐优先采用数据库层面或WordPress原生机制实现,对现有代码改动小,适配性更强:

方案1:添加唯一索引(改动最小)

给lottery表的day_of_lottery字段添加唯一索引,同一天的中奖记录只能写入一次,后续进程执行写入时会触发数据库唯一键冲突,无法插入重复数据。

如果业务规则允许每日多名中奖者,可以改为day_of_lottery + 抽奖批次的联合唯一索引。

方案2:乐观锁原子操作(性能最优)

将原有先查后写的非原子逻辑,改为带条件判断的原子写入,使用INSERT ... SELECT语法:

INSERT INTO {$wpdb->prefix}lottery (winner_mail, time, day_of_lottery, email_log_id)
SELECT %s, %s, %d, %d FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM {$wpdb->prefix}lottery WHERE day_of_lottery = %d)

只有当天无中奖记录时才会写入成功,可通过$wpdb->rows_affected判断写入结果,返回0则直接终止流程。

方案3:WordPress原生Transient锁(适配性最好)

不想修改表结构可以用WP自带的Transient机制实现分布式锁,对现有代码侵入极小:

public function init() {
    $day = +date("j");
    $day--;

    // 加锁,有效期5分钟,避免异常死锁
    $lock_key = "lottery_run_lock_{$day}";
    if (false !== get_transient($lock_key)) {
        return;
    }
    set_transient($lock_key, 1, 300);

    $lastDiceDay = Model::getLastDiceDay();
    if ($lastDiceDay >= $day) {
        delete_transient($lock_key);
        return;
    }

    $dateDay = new \DateTime($this->_from->format('Y-m') . "-$day");
    $mails = Model::getMailsByDay($dateDay);

    $winners = $this->dice($mails, $day);
    Model::saveWinner($winners, $day);
    $this->mail($winners, $day);

    // 执行完成释放锁
    delete_transient($lock_key);
}

内容的提问来源于stack exchange,提问作者Grummel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 13:15:01