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

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

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);
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《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

发生这种情况可能是一个事务未提交(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年前

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

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

看代码没有锁表操作。

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

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

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

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

1年前 评论

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

1年前 评论

出现死锁了

1年前 评论

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

1年前 评论

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

1年前 评论
yyy123456 1年前

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