mtphp 文档

数据库

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 / CLIConnection(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();

10.5 Swoole 协程连接池(高并发核心)

Mtphp\Database\Pool 基于 Swoole\Coroutine\Channel 实现有界队列:

在 Server::onWorkerStart 中按连接名构建并预热池;每个请求从池借用一条连接、存入「协程级上下文」,请求结束归还(见 Server::onRequest)。连接不跨协程共享,天然支持并发。