Hyperf 的 MySQL 协程客户端不内置自动 SQL 重试机制,需开发者按错误类型(如2006、1205、2013)主动实现重试,事务中须每次新建事务,推荐用 AOP 封装声明式重试。

Hyperf 的 MySQL 协程客户端本身不内置自动 SQL 重试机制,它不会在查询失败(比如连接断开、锁超时、死锁)时自动重试语句。所谓“重试”,需要开发者根据错误类型、业务语义和一致性要求,主动设计并实现,不能依赖框架默认行为。
下面从三个实用层面讲清楚怎么做:
明确哪些错误值得重试
不是所有报错都适合重试,盲目重试可能引发数据重复、状态错乱等问题。重点关注以下几类可重试场景:
-
HY000: 2006 MySQL server has gone away:连接被服务端关闭,属于网络/空闲超时问题,重试安全 -
40001 / Deadlock found when trying to get lock:MySQL 死锁错误,官方明确建议重试(需控制次数) -
HY000: 1205 Lock wait timeout exceeded:锁等待超时,视业务容忍度决定是否重试 -
HY000: 2013 Lost connection to MySQL server during query:网络闪断导致,适合短间隔重试
而像 23000 Integrity constraint violation(唯一键冲突)、45000 自定义异常 这类业务逻辑错误,不应重试,应直接处理或抛出。
在事务中安全重试的写法
重试必须包裹在事务外层,且每次重试要开启全新事务,避免复用已污染的连接或事务上下文。推荐用 DB::transaction() + 手动循环:
use HyperfDatabaseDB;
use PDOException;
$maxRetries = 3;
for ($i = 0; $i getCode(), ['2006', '1205', '2013'])) {
co::sleep(0.1 * ($i + 1)); // 指数退避,避免雪崩
continue;
}
throw $e;
}
}
⚠️ 注意:不要在 DB::transaction() 内部手动 beginTransaction(),那会破坏协程连接绑定,容易触发 2006 错误。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
基于 AOP 或装饰器封装通用重试逻辑
为避免每个 DAO 方法都写重复循环,可用 Hyperf 的 Aspect 实现声明式重试:
#[Aspect]
class RetryAspect
{
#[PointExecution("App\Dao\*->*() && !execution(public|protected|private *->__construct(..))")]
public function retry(ProceedingJoinPoint $proceedingJoinPoint)
{
$max = $proceedingJoinPoint->getAnnotation('Retry')?->times ?? 2;
for ($i = 0; $i process();
} catch (PDOException $e) {
if ($i === $max || !in_array($e->getCode(), ['2006', '1205'])) {
throw $e;
}
co::sleep(0.05);
}
}
}
}
再配合注解使用:
#[Retry(times: 2)]
public function updateUserBalance(int $uid, float $delta): bool
{
return DB::update('UPDATE users SET balance = balance + ? WHERE id = ?', [$delta, $uid]) > 0;
}
这样既保持代码干净,又统一了重试策略。
重试不是万能解药,它解决的是瞬时性故障;真正稳定的关键,还是靠 heartbeat + max_idle_time 把僵尸连接挡在业务之外。










