数据库
10.1 配置
config/database.php:支持多连接与读写分离;persistent 用于 FPM 持久连接;pool 用于 Swoole 连接池。
return [
'default' => 'mysql',
'connections' => [
'mysql' => [
'driver' => 'mysql',
'host' => '127.0.0.1',
'port' => 3306,
'dbname' => 'test',
'username' => 'root',
'password' => '',
'charset' => 'utf8mb4',
'prefix' => '',
'persistent' => false, // FPM:true 开启持久连接,降低建连开销
'pool' => ['size' => 16, 'idle' => 10], // Swoole:连接池大小 / 预热空闲数
],
],
];
10.2 连接管理(三种模式)
| 模式 | 连接来源 | 说明 |
|---|---|---|
| FPM / CLI | Connection(PDO) | 懒加载;persistent=true 时跨请求复用底层连接 |
| Swoole 常驻 | CoroutineConnection(Swoole\Coroutine\MySQL)经 Pool 供给 | 协程连接池复用,支撑高并发 |
| 测试 / 轻量 | 任意实现 Connector 接口的对象 | 解耦:查询构造器只依赖 Connector 契约 |
契约接口(Mtphp\Database\Connector):select / statement / insert / update / delete。Connection 与 CoroutineConnection 都实现它,因此上层 Query / DB 在两种模式下无感知切换。
10.3 查询构造器
use Mtphp\Database\DB;
// 查询
DB::table('user')->where('age', '>', 18)->orderBy('id', 'desc')->limit(10)->get();
DB::table('user')->where('status', 1)->where('city', 'BJ')->get();
DB::table('user')->where('name', '张三')->orWhere('name', '李四')->first();
DB::table('user')->find(); // 取第一条
DB::table('user')->value('name'); // 取某列值
DB::table('user')->count(); // 计数(已修复 COUNT(*) 反引号问题)
DB::table('user')->where('age', '>', 18)->exists();
// 写
DB::table('user')->insert(['name' => '张三', 'age' => 30]);
$id = DB::table('user')->insertGetId([...]);
DB::table('user')->where('id', 1)->update(['age' => 31]);
DB::table('user')->where('id', 1)->delete();
Query为链式有状态构造器;每次DB::table()都返回全新实例,互不串味。
10.4 事务(含嵌套 SAVEPOINT)
use Mtphp\Database\DB;
DB::transaction(function () {
DB::table('account')->where('id', 1)->update(['balance' => DB::raw(...)]);
DB::table('log')->insert([...]);
});
// 嵌套(内层失败整体回滚,靠 SAVEPOINT 实现)
DB::transaction(function () {
DB::table('a')->insert([...]);
DB::transaction(function () { /* ... */ });
});
底层事务原语见 Connection::transaction()(PDO)与 Transaction(SAVEPOINT 嵌套)。
嵌套事务已真正接通:
DB::transaction()统一经由Transaction(SAVEPOINT)执行,最外层用BEGIN/COMMIT/ROLLBACK,内层用SAVEPOINT/RELEASE/ROLLBACK TO。嵌套层级按「物理连接(PDO 句柄)」跟踪,因此即便DB::connection()在连接池模式下每次返回新的连接器包装(共享同一 PDO),嵌套也正确;读写分离、协程连接路径同样适用。
10.4.1 悲观锁(SELECT ... FOR UPDATE / FOR SHARE)
并发写场景(如余额扣减、库存扣减)需要先锁住目标行再计算,避免“读-改-写”竞态。Query(以及 Model::query())提供链式悲观锁,须置于事务内才有意义:
use Mtphp\Database\DB;
DB::transaction(function () {
// 写锁:SELECT ... FOR UPDATE(其它事务此时无法修改该行)
$row = DB::table('account')->where('id', 1)->lockForUpdate()->find();
$next = (int) $row['balance'] - 30;
DB::table('account')->where('id', 1)->update(['balance' => $next]);
});
// 读锁(共享锁):SELECT ... FOR SHARE(MySQL 8+;允许多事务并发读,但阻塞写)
DB::table('orders')->where('id', 1)->sharedLock()->get();
// 通用写法:lock('UPDATE') / lock('SHARE')
DB::table('t')->lock('update')->find();
lockForUpdate()→ 追加FOR UPDATE;sharedLock()→ 追加FOR SHARE;lock($mode)为通用入口。- 仅在
SELECT(get/find/first/value/count/...)生效;与where/orderBy/limit等任意链式组合。 - 数据库要求:行级锁需引擎支持——MySQL / PostgreSQL / SQLite ≥ 3.40 原生支持;框架主目标 MySQL 完全可用。不支持的后端(如旧版 SQLite)会自动跳过执行报错,SQL 仍按
FOR UPDATE生成(可用toSql()检查)。
10.5 Swoole 协程连接池(高并发核心)
Mtphp\Database\Pool 基于 Swoole\Coroutine\Channel 实现有界队列:
available():当前是否处于 Swoole 连接池模式;fill($n):worker 启动时预热$n条空闲连接;get($timeout):取连接(池空且未达上限则即时新建,否则等待);put($conn):归还连接。
在 Server::onWorkerStart 中按连接名构建并预热池;每个请求从池借用一条连接、存入「协程级上下文」,请求结束归还(见 Server::onRequest)。连接不跨协程共享,天然支持并发。