协程数据库必须禁用PDO,改用swoole类型驱动:需将database.php中'type'设为'swoole',关闭读写分离、禁用预处理模拟,明文配置数据库凭证,并通过DatabasePool接管Db::connect()实现协程隔离连接。
ThinkPHP 8.0 默认走 PDO,但在 Swoole 协程里它会复用连接、破坏事务隔离、甚至 silently 丢数据。这不是配置没开,而是 PDO 对象本身不支持协程调度——beginTransaction()、setFetchMode() 等调用会污染其他协程状态。
必须把 config/database.php 中的 'type' 显式改成 'swoole'(不是 'mysql' 或 'pdo'),否则 Db::connect() 拿到的还是同步 PDO 实例。
'type' => 'swoole':强制启用 Swoole 原生协程 MySQL 客户端'deploy' => 0:关闭读写分离,否则 Connection 类会绕过协程驱动,偷偷 new PDO'params' => [PDO::ATTR_EMULATE_PREPARES => false]:禁用预处理模拟,否则 SQL 被 PHP 同步拼接,失去协程意义'hostname'、'username'、'password' 必须明文写死,不能用 env() —— 协程上下文不保证环境变量始终可读默认 Db::connect() 是单例 + 连接复用,协程里多个请求拿到的是同一个 PDO 对象。你手动 new PDO() 也不行——生命周期难管理,容易漏关、OOM。
真正安全的做法是用 think-swoole 自带的 DatabasePool 替换连接创建逻辑:
立即学习“PHP免费学习笔记(深入)”;
app/provider.php 或自定义服务提供者中绑定:Container::getInstance()->bind('db', function($app) { ... });
DatabasePool 会按 SwooleCoroutine::getuid() 分配独占连接,每个协程拿到隔离实例max_request * 2(如 Swoole 配置 max_request => 3000,池大小设 60–100);太大易 OOM,太小导致频繁创建销毁如果只开了 enable_coroutine => true 却没设 max_coroutine,协程内发起的异步 DB 查询会被阻塞,Db::transaction() 直接卡死——因为协程数超限后,新协程排队等资源,而事务又锁着连接不放。
'max_coroutine' => 3000 是常见安全值,可根据并发峰值微调'worker_num' 建议设为 CPU 核数 × 2~4,超过 32 很少带来收益,反而加剧进程间锁竞争'task_worker_num' 留 2~4 个足够,日志、邮件这类耗时操作才扔进去;Redis 缓存更新本就是协程安全的,别误丢进 task直接在 request 回调里跑 $app->http->run() 会导致请求上下文残留,静态属性、全局变量、Session 数据跨请求污染。
每个请求必须包裹在 go() 里,并显式清理:
go(function() use ($app, $request, $response) { ... }) 启动独立协程$app->request->clear() 和 $app->session->flush()
try/catch 捕获,否则 worker 进程崩溃,整个服务挂掉协程安全不是加个 go 就完事,连接池、上下文清理、错误兜底这三块缺一不可——漏掉任意一个,上线后都可能表现为偶发卡顿、事务失败或数据错乱。