数据库配置

数据库配置文件为 config/database.php,以通道(Channel)为组织单元管理数据库连接:每个通道对应一组驱动与连接选项,并持有独立的连接池(Connection Pool)。

本文依据框架默认配置 config/database.php 及源码 src/Database/DbManager.phpsrc/Database/Channel/PDO/{PDOChannel,PDOConfig,DriverType}.php 编写。

配置文件结构

php
<?php
// config/database.php
use Viswoole\Database\Channel\PDO\PDOChannel;
use Viswoole\Database\Facade\Db;

return [
  // 默认通道名称
  'default' => env('DATABASE_DEFAULT', 'default'),
  // 是否开启调试模式(打印/记录 SQL 运行信息)
  'debug' => env('app_debug', true),
  // 调试信息保存方式:1 控制台,2 日志文件,3 同时保存(位运算组合)
  'info_save_manner' => Db::DEBUG_SAVE_CONSOLE | Db::DEBUG_SAVE_LOGGER,
  // XA 两阶段提交事务配置(详见「XA 配置」一节)
  'xa' => [
    // 是否在每个 worker 启动时自动执行 XA 崩溃恢复(默认关闭,
    // 悬挂事务经 php viswoole xa:recover 手动/定时收敛)
    'auto_recovery' => env('DATABASE_XA_AUTO_RECOVERY', false),
    // journal 表所在通道名,留空使用默认通道
    'journal_channel' => env('DATABASE_XA_JOURNAL_CHANNEL', ''),
    // journal 表名,首次执行 XA 事务时自动建表
    'journal_table' => env('DATABASE_XA_JOURNAL_TABLE', 'viswoole_xa_journal'),
  ],
  // 通道列表
  'channels' => [
    'default' => [
      // 驱动类,必须继承 Viswoole\Database\Channel
      'driver' => PDOChannel::class,
      // 驱动构造参数
      'options' => [
        'host' => env('DATABASE_HOST', '127.0.0.1'),
        'port' => (int)env('DATABASE_PORT', 3306),
        'database' => env('DATABASE_NAME', ''),
        'username' => env('DATABASE_USER', 'root'),
        'password' => env('DATABASE_PASSWORD', '123456'),
      ],
    ],
  ],
];

顶层配置

配置项类型默认值说明
defaultstringdefault默认使用的通道名称,Db::channel() 不传参时使用
debugbooltrue调试模式开关,控制 SQL 运行信息的输出(生产环境建议关闭)
info_save_mannerint3调试信息保存方式,按位组合:1 控制台、2 日志文件
channelsarray通道列表,键为通道名(不区分大小写)
xaarray见下XA 事务 journal 配置,见下节

调试信息保存方式

info_save_manner 使用 Db 门面提供的位常量,通过 | 组合:

常量说明
Db::DEBUG_SAVE_CONSOLE1SQL 信息输出到终端控制台
Db::DEBUG_SAVE_LOGGER2SQL 信息写入日志文件
php
// 仅写入日志文件
'info_save_manner' => Db::DEBUG_SAVE_LOGGER,

运行时切换调试模式

调试配置通过 Swoole\Table 跨进程共享,可在运行中动态调整:Db::setDebug(bool $debug) 开关调试,Db::setDebugInfoSaveManner(int $manner) 调整保存方式,对所有 Worker 进程即时生效。

通道配置(channels)

每个通道包含两个键:

配置项类型说明
driverstring通道驱动类名,须继承 Viswoole\Database\Channel,内置 PDOChannel
optionsarray传给驱动构造函数的命名参数

PDO 通道选项(options)

options 的键与 PDOChannel 构造函数参数一一对应:

参数类型默认值说明
typeDriverTypeDriverType::MYSQL驱动类型枚举,见下表
hoststring | array127.0.0.1主机地址;传 ['read' => ..., 'write' => ...] 数组启用读写分离
portint3306端口
stickybooltrue粘性读:同一协程写入后的后续读操作复用写连接池
databasestringtest数据库名称
usernamestringroot用户名
passwordstringroot密码
charsetstring |utf8mb4字符集
timezonestring | null |null连接时区:null 对齐应用时区,字符串使用指定时区(命名时区或 UTC 偏移),空串 '' 不设置;仅 MySQL 与 PostgreSQL 生效
optionsarray[]附加 PDO 属性(如 PDO::ATTR_TIMEOUT
pool_max_sizeint10连接池最大连接数
pool_fill_sizeint0连接池初始填充数,0 表示不预填充
pool_timeout_timeint5获取/归还连接的超时时间(秒)

连接时区对齐

timezone 默认 null 时连接时区对齐应用时区(date_default_timezone_get()):MySQL 经 MYSQL_ATTR_INIT_COMMAND 注入 SET time_zone(命名时区自动转为 UTC 偏移,不依赖服务端时区表),PostgreSQL 经 DSN options 注入;SQLite/Oracle/SQL Server 暂不支持。用户已设置 MySQL INIT_COMMAND 时时区语句追加而非覆盖。

Unix Socket 连接

host/ 开头且以 .sock 结尾时,框架自动识别为 Unix Socket 路径并优先于 TCP 连接使用,例如 /tmp/mysql.sock

驱动类型(DriverType)

type 使用 Viswoole\Database\Channel\PDO\DriverType 枚举,枚举值对应 PDO DSN 的驱动标识:

枚举成员PDO 驱动数据库
DriverType::MYSQLmysqlMySQL / MariaDB
DriverType::POSTGRESQLpgsqlPostgreSQL
DriverType::ORACLEociOracle
DriverType::SQLitesqliteSQLite
DriverType::SQLServersqlsrvSQL Server

连接池参数

Swoole 常驻内存下连接不能每次请求重建,框架为每个通道维护连接池,协程内借出、用完自动归还:

  • pool_max_size:池内连接数上限,即该通道的最大并发连接数。并发高峰时超出的协程会挂起等待空闲连接,等待超时抛出异常。应根据数据库 max_connections 与 Worker 数量估算,避免打满数据库连接。
  • pool_fill_size:服务启动时预创建的连接数,默认 0(首次请求时按需创建)。流量稳定的预热场景可设置为与 pool_max_size 相同,避免冷启动时集中建连。
  • pool_timeout_time:从池中获取连接(含事务期间持有连接)的最大等待秒数,默认 5。超时通常意味着并发数超过了 pool_max_size 或存在慢查询占住连接。

请勿手动缓存连接

连接的借出与归还由连接池和协程上下文自动完成,业务代码不要把连接对象存到静态属性或容器单例中,否则会破坏协程间的连接隔离。

连接归属与 Master/Manager 短连接

连接池归 Worker 进程所有:连接只在 Worker/TaskWorker 触发 workerStart 时独立建立与计数,总连接数 = 单 Worker 池内占用 × Worker 数量,Master/Manager 进程不会额外持有 N × pool_max_size 条连接。

若在服务启动阶段或 Master/Manager 进程回调(onStart / onManagerStart 等)中使用 Db,每次操作走一次性短连接(直连直关、不经过连接池):执行完毕连接即销毁,不会长期占用数据库连接数。服务启动之前(如 AppInitialized 事件、命令行脚本)进程处于 CLI 状态,协程内照常使用连接池,非协程脚本等价为短连接。

自定义 Swoole\Process 子进程默认同样走短连接;此类进程为常驻进程、如需使用连接池,可在进程回调首行调用 Viswoole\Core\Server\ProcessRole::markAsWorker()

避免在非 Worker 进程高频操作数据库

短连接模式下每条语句各建一次连接(握手 + 认证),高频循环会产生大量 TIME_WAIT 套接字并显著增加耗时。此类场景有两种规避方式:通过 Task::emit 将任务投递到工作进程执行,由工作进程的连接池承接;或手动借还连接,在 pop/put 窗口内循环复用同一条连接执行原生 SQL:

php
$channel = Db::channel();
$conn = $channel->pop('write');           // 取连接(此时才建连)
try {
    $stmt = $conn->prepare('SELECT * FROM orders WHERE status = ?');
    $stmt->execute([1]);
    $rows = $stmt->fetchAll();
    // 循环内继续复用 $conn,不再逐语句建连
} finally {
    $channel->put($conn);                 // 归还(此刻才关闭)
}

读写分离

host 传入数组即可启用读写分离,读库与写库各自支持多个地址(字符串或字符串数组),多个读库之间按轮询(Round-Robin)分发:

php
use Viswoole\Database\Channel\PDO\PDOChannel;

'channels' => [
  'default' => [
    'driver' => PDOChannel::class,
    'options' => [
      // 读库走 192.168.1.20,写库走 192.168.1.10
      'host' => [
        'read' => ['192.168.1.20', '192.168.1.21'],
        'write' => '192.168.1.10',
      ],
      'database' => 'app',
      'username' => 'root',
      'password' => env('DATABASE_PASSWORD', ''),
    ],
  ],
],

调度规则(见 PDOChannel 源码):

  • SELECT 等查询语句路由到读库,其余语句路由到写库;
  • sticky = true(默认)时,同一协程内一旦发生写入,后续读操作继续复用写库,避免主从延迟读到旧数据;
  • FOR UPDATE / LOCK IN SHARE MODE 等锁定读强制路由到写库,保证锁语义成立;
  • 事务期间的所有操作统一使用写库连接。

读写分离场景下,原生查询 Db::query($sql, $bindings, true) 的第三个参数可强制从主库读取,详见查询构造器 — 原生查询

多通道

channels 中定义多个通道即可实现多库访问,通过 Db::channel('通道名') 切换,不传参时使用 default 通道:

php
use Viswoole\Database\Facade\Db;

// 使用默认通道
Db::table('user')->where('id', 1)->find();

// 切换到 order 通道(订单库)
Db::channel('order')
    ->table('order')
    ->where('user_id', 1)
    ->select();

// 判断通道是否已注册
Db::hasChannel('order');

ORM 模型通过 $channelName 属性绑定通道,详见 ORM 模型

动态注册通道

除配置文件外,可在服务启动前通过 Db::addChannel() 注册通道:

php
use Viswoole\Database\Channel\PDO\PDOChannel;
use Viswoole\Database\Facade\Db;

// 通道实例、驱动类名或 ['driver' => ..., 'options' => [...]] 配置数组均可
Db::addChannel('order', [
    'driver' => PDOChannel::class,
    'options' => [
        'host' => '192.168.1.30',
        'database' => 'order',
        'username' => 'root',
        'password' => 'secret',
    ],
]);

必须在服务启动前注册

addChannel() 需在 Swoole 服务启动之前调用(例如监听 AppInitialized 事件或服务提供者注册阶段)。在 Worker 进程运行期添加的通道仅存在于当前进程,不会同步到其他进程。

XA 配置

xa 节配置 XA 两阶段提交事务的 journal(提交意图日志,崩溃恢复的决策依据):

配置项类型默认值说明
auto_recoveryboolfalse是否在 worker 启动时自动执行 XA 崩溃恢复;关闭时经 php viswoole xa:recover 手动/定时收敛
journal_channelstring''(默认通道)journal 表所在通道名,须为支持 XA 的 MySQL 通道
journal_tablestringviswoole_xa_journaljournal 表名,首次执行 XA 事务时自动建表(恢复路径只探测不建表,未使用 XA 的项目不会出现该表)

journal 通道通常指向参与事务的库之一,不引入额外部署组件。更多说明见 XA 事务

下一步