两个队列任务同时操作一张表导致锁表,任务失败,请问如何解决?

两个队列任务同时操作一张表导致锁表,任务失败,请问如何解决?

Redis队列两个任务有几率同时更新一张表数据,导致报错
Deadlock found when trying to get lock;try restarting transaction

应该是两个相同任务,同时执行,操作了同一个表,导致的,是我修复逻辑不让他们通知操作一张表,还是有办法,让他们可以同时操作同一张表?


// 下面这段代码按照条件筛选出的数据更新外键 
// 操作表 collect_data
$collect->where('keywords', '=', $goods_item['keywords'])
                    ->where('title', 'not like', '拍拍    %') // *** 搜索条件并非空格,是特殊字符(全角空格?) ***
                    ->where($orWhereBrand)
                    ->where($orWhereSKU)
                    ->where($whereFilter)
                    ->where($whereBlock)
                    ->where($orWhereShop)
                    ->where($whereShopBlock)
                    ->update(['goods_uuid' => $goods_item['uuid']]);

// 下面这段代码是更新主表商品状态,如果关联字表价格低于主表商品,商品状态更新为2 否则3 
// 主表 goods 子表 collect_data
            $update_sql = 'UPDATE ' . $goods_table . ' AS goods
                INNER JOIN (SELECT goods_uuid, MIN(price) price FROM ' . $collect_table . ' GROUP BY goods_uuid) AS collect_data
                ON goods.uuid = collect_data.goods_uuid
                SET goods.is_warning = IF(collect_data.price < goods.price, 2, 3)
                WHERE collect_data.goods_uuid = "' . $goods_item['uuid'] . '"';
            DB::update($update_sql);
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
最佳答案

发生这种情况可能是一个事务未提交(mysql默认是开启自动提交事务的),另一个事务已经提交。导致的锁表或者锁行。

file

file

可以手动加锁,上一条未执行完毕,后面的需要等待。

可以使用redis加锁,然后队列中锁住的条件进行重试。条件用查询的唯一条件拼接。

这是之前参考论坛里大佬写的 service 你也可以参考一下。

<?php

namespace App\Services;

use Illuminate\Support\Facades\Redis;

/**
 * Redis 锁
 * 保证原子性
 */
class RedisLockService
{
    /**
     * 随机值
     * @var string
     */
    private string $key = 'lock-token';

    /**
     * 设置 key
     *
     * @param string $key
     * @return $this
     */
    public function setKey(string $key): static
    {
        $this->key = $key;
        return $this;
    }

    /**
     * 加锁
     *
     * @param string $token
     * @param int $expire 过期时长
     * @return mixed
     */
    public function lock(string $token, int $expire = 10): mixed
    {
        return Redis::set($this->key, $token, "ex", $expire, "nx");
    }

    /**
     * 解锁
     *
     * @param string $token 值
     * @return mixed
     */
    public function unlock(string $token): mixed
    {
        // LUA 脚本
        $script = "
            if redis.call('get',KEYS[1]) == ARGV[1]
            then
                return redis.call('del',KEYS[1])
            else
                return 0
            end
        ";
        return Redis::eval($script, 1, $this->key, $token);
    }
}
1年前 评论
zhy 1年前
zhy 1年前
ImVic (楼主) 1年前
看上隔壁小花了啦 (作者) 1年前
zhy 1年前
看上隔壁小花了啦 (作者) 1年前
讨论数量: 23

这个你最起码发下sql或者伪代码啊

1年前 评论
ImVic (楼主) 1年前
ImVic (楼主) 1年前

看代码没有锁表操作。

如果认为是锁表造成的队列,可以根据实际业务情况,对队列设置合理的重试次数以及重试规则。

例如,在队列确认是否是锁表造成的异常,则在20秒后进行重试处理。如果3次都因为同一问题失败,则标记为队列处理失败。

1年前 评论
ImVic (楼主) 1年前
ImVic (楼主) 1年前
24K大白羊 (作者) 1年前
ImVic (楼主) 1年前
zhy 1年前
ImVic (楼主) 1年前

我怎么觉得这种关联修改非常容易导致触发表锁呢?

1年前 评论

你的解释不对吧,想要避免冲突应该使用悲观锁。

1年前 评论

发生这种情况可能是一个事务未提交(mysql默认是开启自动提交事务的),另一个事务已经提交。导致的锁表或者锁行。

file

file

可以手动加锁,上一条未执行完毕,后面的需要等待。

可以使用redis加锁,然后队列中锁住的条件进行重试。条件用查询的唯一条件拼接。

这是之前参考论坛里大佬写的 service 你也可以参考一下。

<?php

namespace App\Services;

use Illuminate\Support\Facades\Redis;

/**
 * Redis 锁
 * 保证原子性
 */
class RedisLockService
{
    /**
     * 随机值
     * @var string
     */
    private string $key = 'lock-token';

    /**
     * 设置 key
     *
     * @param string $key
     * @return $this
     */
    public function setKey(string $key): static
    {
        $this->key = $key;
        return $this;
    }

    /**
     * 加锁
     *
     * @param string $token
     * @param int $expire 过期时长
     * @return mixed
     */
    public function lock(string $token, int $expire = 10): mixed
    {
        return Redis::set($this->key, $token, "ex", $expire, "nx");
    }

    /**
     * 解锁
     *
     * @param string $token 值
     * @return mixed
     */
    public function unlock(string $token): mixed
    {
        // LUA 脚本
        $script = "
            if redis.call('get',KEYS[1]) == ARGV[1]
            then
                return redis.call('del',KEYS[1])
            else
                return 0
            end
        ";
        return Redis::eval($script, 1, $this->key, $token);
    }
}
1年前 评论
zhy 1年前
zhy 1年前
ImVic (楼主) 1年前
看上隔壁小花了啦 (作者) 1年前
zhy 1年前
看上隔壁小花了啦 (作者) 1年前

出现死锁了

1年前 评论

我觉得可以简单点,把表拆出来,两个字段分别对应两张表,和两个任务对应,这样执行就不会有锁表的问题了

1年前 评论

如果是 多任务频繁操作同一业务表,最好是通过redis来使用逻辑加/释放锁的操作来控制,,单单是让mysql的事务,肯定会经常发生死锁的

1年前 评论
yyy123456 1年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!