不建议在 pcntl_signal 回调中执行数据库 IO。因其运行在中断上下文,可能打断事务、导致锁冲突、连接状态错乱及死锁;应仅做标记(如 $shouldExit = true),由主循环检查后安全执行数据库操作。

不建议在 pcntl_signal 回调中执行数据库 IO,尤其是涉及事务、锁或长连接的操作。PHP 的信号处理本身是异步且非重入的,而数据库操作(如 PDO/MySQLi 查询、事务提交、连接复用)依赖于主线程状态、连接上下文和锁资源,在信号中断上下文中执行极易引发不可预测行为——包括连接中断、事务状态错乱、连接池污染,以及你观察到的死锁。
为什么信号回调里做数据库操作会死锁
根本原因在于信号处理函数运行在「中断上下文」,它可能打断正在执行的数据库操作(比如一个未完成的 UPDATE 或事务中的 SELECT ... FOR UPDATE),导致:
- 原事务持有的行锁/间隙锁未释放,而信号回调又尝试获取相同或冲突的锁
- PDO/MySQLi 连接对象非线程安全,也非信号安全;中断时重用同一连接可能使内部状态(如 pending result、autocommit 标志)错乱
- PHP 的
pcntl_signal在 7.1+ 虽支持siginfo,但底层仍依赖 tick 或pcntl_signal_dispatch()轮询,回调并非真正“实时”,而是延迟进入 PHP 用户空间,此时数据库连接可能已处于半关闭或超时等待状态
推荐替代方案:把数据库操作移出信号回调
核心原则:信号回调只做「标记 + 通知」,真实 IO 放回主循环中由业务逻辑统一处理。
-
用全局状态或共享内存标记信号到达:例如设置
Status::$shutdownRequested = true或使用pcntl_shmop_*()写入标志位 -
主循环中定期检查并响应:在 while 循环里调用
pcntl_signal_dispatch()(确保信号队列被清空),再判断状态变量,执行数据库清理、事务提交、日志落盘等安全操作 -
配合
pcntl_async_signals(true)(PHP 7.1+ 推荐):启用异步信号处理后,可避免 tick 开销,但仍需坚持「回调零 IO」原则
更健壮的进程退出模式(适用于守护进程/Worker)
以优雅关闭为例,推荐结构:
立即学习“PHP免费学习笔记(深入)”;
- 父进程发送
SIGTERM给子 Worker - Worker 的信号处理器仅设置
$shouldExit = true并返回 - Worker 主循环检测
$shouldExit后:
→ 先关闭新请求接入(如停用 Swoole server 的 accept)
→ 等待正在处理的任务完成(带超时)
→ 执行数据库事务提交/回滚、释放资源、写退出日志
→exit(0)
若必须在信号路径触发持久化,考虑无锁旁路方案
避开数据库直连,改用更轻量、信号友好的方式记录意图:
- 写入临时文件(
file_put_contents('shutdown.flag', '1', LOCK_EX)),主循环轮询该文件 - 通过
sysvmsg_send()发送简单控制消息到主进程,由主进程代为执行 DB 操作 - 使用 Redis 的
SET shutdown:pid 1 EX 30 NX做原子标记,再由独立清理服务监听并执行后续动作



















