# database **Repository Path**: fiberphp/database ## Basic Information - **Project Name**: database - **Description**: 🗄️ FiberPHP 数据库核心抽象 —— 连接管理器、查询构造器、语法器接口,不包含具体驱动实现,供驱动包继承扩展。 - **Primary Language**: PHP - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-22 - **Last Updated**: 2026-10-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # fiberphp/database 数据库核心包 FiberPHP 数据库组件:协程连接池 + 查询构造器 + SQL 生成 + 驱动方言抽象。内置 MySQL / PostgreSQL / SQLite 三驱动。 ## 特性 - **连接池**:workerman 协程环境自动池化(挂起等待/心跳探活/上限控制);无 workerman 时回退内置 `SyncPool`(同步复用),可脱离框架独立使用 - **驱动方言抽象**:方言差异收敛到 `AbstractDriver`(驱动模板基类),SQL 生成为无状态 Builder(可注册扩展解析器) - **查询缓存 / 延迟写入 / JSON 字段 / 游标流式读取 / 光标分页** - **慢查询诊断**:超阈值 SELECT 自动 EXPLAIN 并写日志;`query` 事件可监听 - **表结构缓存**:schema 元信息三层递进(运行时 → cache 包 → 数据库),支持按表清理 ## 环境要求 - PHP >= 8.3、ext-pdo - 可选:workerman/workerman(协程池,缺失时回退内置 SyncPool) - fiberphp/cache 为可选依赖(缺失时查询缓存 / 延迟写入 / 表结构缓存均优雅跳过) ## 安装 ```bash composer require fiberphp/database ``` ## 配置(config/database.php) ```php return [ 'default' => 'mysql', 'connections' => [ 'mysql' => [ 'driver' => 'mysql', // mysql | pgsql | sqlite 'host' => env('DB_HOST', '127.0.0.1'), 'database' => env('DB_DATABASE', 'demo'), 'username' => env('DB_USERNAME', 'root'), 'password' => env('DB_PASSWORD', ''), 'port' => (string)env('DB_PORT', 3306), 'charset' => 'utf8mb4', 'prefix' => '', // 表前缀 'params' => [PDO::ATTR_TIMEOUT => 3], 'schema_cache' => false, // 表结构缓存(跨进程) 'pool' => [ // 连接池 'max_connections' => 5, 'min_connections' => 1, 'wait_timeout' => 3, 'idle_timeout' => 60, 'heartbeat_interval' => 50, ], 'slow_sql_threshold' => 1000, // 慢查询阈值(ms),0=不检测 ], ], 'log_sql' => true, // SQL 日志 'log_channel' => 'db', // 日志通道名 ]; ``` ## 快速上手 ```php use FiberPHP\Database\Db; $q = db()->newQuery(); // 显式入口(推荐);db()->name('user') 经 __call 亦可 // 查 db()->name('user')->where([['id', '=', 1]])->find(); // array|null db()->name('user')->where(['status' => 1])->select(); // Collection db()->name('user')->where(['status' => 1])->count(); // int // 增删改 db()->name('user')->insert(['name' => 'tom', 'age' => 18]); db()->name('user')->where([['id', '=', 1]])->update(['age' => 19]); db()->name('user')->delete(1); // 按 PK 删 // 多连接:db()->name('user') 走 default;db('连接名') 返回该连接上的查询对象 db('log')->name('access_log')->find(); // 显式入口(语义同上):db()->newQuery('log')->name('access_log')->find() ``` ## API 全集 ### 表与别名 ```php $q->name('user') // 自动加前缀+snake(推荐) $q->name('user')->alias('u') // 别名 $q->tableRaw('user FORCE INDEX(idx)') // 原生表表达式(不转义、不拼前后缀) $q->setTableSuffix('_2026') // 表名后缀(分表) $q->getTable() // 当前表名 ``` ### 表结构与元信息 ```php $q->getTables() // 当前库所有表名(可传库名) $q->getTableFields('user') // 字段名数组 $q->getFieldsType('user') // 字段 => 类型映射 $q->getPrimaryKey('user') // 主键(array|string|null,无主键返回 null) $q->setPk(['id', 'tenant_id']) // 查询级覆盖主键(复合主键传数组) $q->getSchemaInfo('user') // 表结构信息(含缓存,true 强制刷新) $q->clearSchemaCache('user') // 清理单表 schema 缓存(null 清全部) ``` ### 字段选择 ```php $q->field(['id', 'name']) // 列表数组(仅接受数组) $q->field(['name' => 'nickname']) // 别名映射 → name AS nickname $q->field([new Raw('COUNT(*)')]) // Raw 对象元素 $q->withoutField(['password']) // 排除字段 $q->fieldRaw('COUNT(*) AS c') // 原生字段(显式字符串通道) $q->distinct() // DISTINCT ``` ### 条件(WhereQuery) `where()` / `whereOr()` 只接受**数组**或**闭包**,数组有两种形态: ```php // 关联 map:仅支持等值(=)与 null(IS NULL),AND 连接 $q->where(['status' => 1, 'deleted_at' => null]) // list:每项必须是恰好三段的 [字段, 操作符, 值] $q->where([ ['id', '>', 10], ['age', '<>', null], // 值为 null:=/<>/!= 转 IS NULL / IS NOT NULL ]) $q->whereOr([['status', '=', 2]]) // OR 组,形态与 where 相同 // 闭包子条件(括号分组) $q->where(fn($q) => $q->where([['a', '=', 1]])->whereOr([['b', '=', 2]])) $q->where([]) // 空数组 no-op ``` IN / LIKE / BETWEEN / NULL 等直接在三段元组中写操作符: ```php $q->where([['id', 'IN', [1,2,3]]]) $q->where([['age', 'BETWEEN', [18, 30]]]) // 值必须是两元素数组 $q->where([['name', 'LIKE', '%tom%']]) $q->where([['deleted_at', '=', null]]) // IS NULL;'<>' 归一 IS NOT NULL ``` 其余语义明确的专用方法: ```php $q->whereExists(fn($q) => ...) / whereNotExists() $q->whereColumn('score', '>', 'base_score') // 字段与字段比较 $q->whereExp('age', '+ 1 > 20', $bind) // 字段 + 表达式尾部(带绑定) $q->whereRaw('YEAR(created_at) = :year', ['year' => 2026]) // 原生 AND / whereOrRaw 原生 OR $q->whereFieldRaw('created_at', '>', 'NOW()') // 字段侧原生值 $q->whereJsonContains('ext->tags', 'php') // JSON 包含(三方言适配,OR 用 whereOr(fn) 包裹) // 条件触发(动态查询) $q->when($kw, fn($q) => $q->where([['name', 'LIKE', "%$kw%"]]), fn($q) => $q->where([['status', '=', 1]])) ``` ### 时间条件(TimeFieldQuery) ```php $q->whereTime('created_at', '>', '2026-01-01') $q->whereDay('created_at') // 今天(可传 'yesterday'、日期、步长) $q->whereWeek('created_at') // 本周 $q->whereMonth('created_at') // 本月 $q->whereYear('created_at') // 今年 $q->whereBetweenTime('created_at', '09:00', '18:00') $q->whereNotBetweenTime('created_at', '00:00', '06:00') $q->whereBetweenTimeField('start_at', 'end_at') // 跨字段区间(NOW() 在区间内) $q->whereTimeInterval('created_at', '2026-01-01 08:00', 'hour', 2) // 起点+步长构造 between 区间 $q->timeRule(['today' => fn() => [...]]) // 自定义时间规则 ``` ### 排序 / 分组 / 技巧 ```php $q->order(['id' => 'desc']) $q->order(['status' => 'asc', 'id' => 'desc']) // map:键=字段,值=asc/desc $q->order(['id', 'create_time']) // list:裸字段,默认方向 $q->orderRaw('FIELD(id, 3,1,2)') // 原生表达式(可用 Raw list 元素替代) $q->orderField('status', [2, 1, 3]) // 按值枚举排序 $q->orderRand() $q->group(['type'])->having('COUNT(*) > 5') $q->group([new Raw('DATE(created_at)')]) // 表达式分组(函数/JSON 提取等) $q->force('idx_user_status') // FORCE INDEX(MySQL) $q->partition(['p2026']) // 分区表(MySQL) $q->comment('后台导出') // SQL 注释 ``` ### 关联与联合 ```php $q->join('profile p', ['p.user_id = u.id']) $q->leftJoin('profile p', ['p.user_id = u.id']) // rightJoin / fullJoin(Pgsql) $q->using('user_id') // USING 单字段 $q->via('u') // 关联字段自动加前缀 $q->union(fn($q) => $q->name('admin')->field(['id', 'name'])) $q->unionAll(...同理) $q->buildSql() // 生成子查询 SQL(( ... )) ``` ### 聚合 ```php $q->count() $q->count('DISTINCT uid') $q->sum('score') $q->avg('score') $q->min('age') $q->max('age') $q->exists() // bool,是否命中 $q->value('name') // 单值 $q->column('name') // 一维列表 $q->column('name', 'id') // id => name 映射 ``` ### 终态操作(Query 层) ```php // 查 $q->select() // Collection(空抛错可用 selectOrFail) $q->find(1) // 主键查单条 / find(null) 走 where $q->findOrEmpty(1) // 空返回 [] 不报错 $q->findOrFail(1) // 空抛 DataNotFoundException $q->allowEmpty() // 允许空结果(与 selectOrFail 配合) $q->failException() // 空结果抛异常(调试用) // 增 $q->insert(['name' => 'tom']) $q->insertGetId(['name' => 'tom']) // 返回自增 ID $q->insertAll([row1, row2, ...], 500) // 批量(自动分批 500/批 + 事务) $q->name('tmp')->insertAll($rows) // Pgsql 走 ON CONFLICT 需要 duplicate() $q->replace(true)->insert($row) // REPLACE INTO(MySQL) $q->name('user')->duplicate(['age' => 30]) // UPSERT:MySQL ON DUPLICATE / Pgsql ON CONFLICT ->insert(['uid' => 1, 'age' => 30]) $q->selectInsert(['id','name'], 'user_bak') // INSERT INTO ... SELECT // 改 $q->where([['id', '=', 1]])->update(['age' => 19]) $q->inc('score', 5)->update() // score = score + 5 $q->dec('balance', 10)->update() $q->inc('views', 1, 60)->update() // 延迟写入:60 秒窗口内合并(需 cache) // 删 $q->delete(1) // 主键 $q->where(['status' => 0])->delete() // 原生 $q->execute('UPDATE user SET age = age + 1 WHERE id > :min_id', ['min_id' => 100]) // int 影响行数 $q->query('SELECT * FROM user LIMIT 10') // array(连接层快捷) $q->batchQuery(['SET NAMES utf8mb4', 'TRUNCATE TABLE tmp']) // 批量执行 SQL 脚本 // 调试 $q->fetchSql(true)->find() // 返回 SQL 字符串而不执行 $q->getLastSql() // 最后执行的 SQL $q->buildSql() // 构造 SQL(子查询用) $q->getPdo() // 原生 PDOStatement $q->extra('FOR UPDATE') // 追加子句(原样拼接,勿拼用户输入) // 参数绑定(命名占位符,不支持 ?) $ph = $q->bindValue(1) // 返回生成的占位符名(值注册在当前查询) $q->autoBind(false) // 关闭自动参数绑定(连接配置 auto_bind 可全局控制) ``` ### JSON 字段 ```php $q->json(['ext']) // 声明 ext 为 JSON 字段(读写自动编解码) $q->json(['ext'], true) // 解码为关联数组 $q->whereJsonContains('ext->tags', 'php') // JSON 条件 ``` ### 结果处理(ResultOperation) ```php $q->filter(fn($row) => $row) // 逐行加工 $q->filter(fn($row) => $row, 'id') // 同上并按 id 为键 $q->readonly(['internal_note']) // 只读字段(写入时自动剥离) $q->schema(['id' => 'int', 'score' => 'float']) // 手动声明字段类型(免 schema 探测) $q->fieldMap(['uid' => 'id']) // 输出字段名映射 $q->strict(false) // 写入时允许非表字段(默认丢弃) ``` ### 大数据量:游标 / 分块 / 惰性 ```php foreach ($q->cursor() as $row) {} // Generator 逐行,默认 buffered foreach ($q->cursor(true) as $row) {} // unbuffered:最省内存(独占连接至迭代完) $q->chunk(1000, function ($rows) { ... }) // 按主键分块回调,返回 false 终止 foreach ($q->lazy(1000) as $rows) {} // LazyCollection(按主键倒序分块,惰性流) $q->stream(function (LazyCollection $rows) { // 大表流式处理 foreach ($rows as $row) { ... } }); ``` LazyCollection 方法:`map/filter/each/take/skip/page/first/last/count/isEmpty/reduce/when/chunk/toArray/toJson/jsonSerialize`(惰性安全操作集)。 ### 分页(Paginatable) ```php $paginator = $q->where(['status' => 1])->paginate(15); // Paginator(页码分页) $simple = $q->simplePaginate(15); // 无总数(只算下一页) $cursor = $q->order(['id'])->cursorPaginate(20); # CursorPaginator(offset 游标编码,无 COUNT) $paginator->items() / currentPage() / lastPage() / total() / hasMorePages() $cursor->nextCursor() / previousCursor() // CursorPaginator 游标令牌(null = 到末尾/已在首页) ``` ### 事务 ```php // 回调式(推荐) db()->transaction(function () { db()->name('user')->insert($row); db()->name('log')->insert($log); }); // 任一异常自动回滚并上抛 // 手动事务:经连接操作,支持保存点嵌套与断线重连重试 // startTrans 内层自动 SAVEPOINT transN;commit 内层只释放保存点,最外层才 COMMIT // rollback 内层回滚到保存点;断线重建后 commit/rollback 显式抛 DbException(防误判已提交) $conn = db()->connect(); $conn->startTrans(); try { db()->name('user')->insert($row); $conn->commit(); } catch (Throwable $e) { $conn->rollback(); throw $e; } ``` > **事务事件钩子**:database Transaction trait 自动调用 `FiberPHP\Event\Event::beginTransaction()/commit()/rollback()`,emit 派发的事件在事务内暂存,commit 后才 flush(afterCommit 语义),rollback 时丢弃。三处钩子均加了 try/catch 守卫,监听器异常不冒泡阻断数据库提交。 ### 查询缓存(QueryCache,需 fiberphp/cache) ```php $q->cache('user_list', 300)->select() // 键 + TTL(秒) $q->cacheAlways('user_list', 300)->select() // 忽略缓存直查后回写 $q->cacheForce('user_list', 300)->select() // 强制(升级场景绕过 tag 失效) // 失效:cache()->tag('user_list')->clear() —— tag 随查询自动登记 ``` ## 连接与池 ```php use FiberPHP\Database\Db; use FiberPHP\Database\Pool\SyncPool; $db = new Db($config); // 独立使用(无容器) // 自定义池 $db->setPoolFactory(function (string $name, array $poolConfig) { return new SyncPool($poolConfig['max_connections'] ?? 10); }); $conn = $db->connect(); // 借出连接(协程环境自动复用) $conn->close(); // 显式关闭 $conn->isClosed(); // 池归还前由框架调用 $conn->rollbackHangingTransaction(); // 归还前回滚悬挂事务 ``` - workerman 存在(框架运行时)→ `WorkermanPool`:协程挂起等待、心跳探活、空闲回收 - 无 workerman → `SyncPool`:空闲队列复用 + 借出前探活 + 上限控制(同步场景) - 连接归还时自动:回滚悬挂事务 → 关闭游标 → 归还池;Context 销毁时兜底回收 ## 事件与慢查询诊断 database 有两套事件机制: **CRUD 事件(强类型 Event 类,走 framework 内置事件总线)**: ```php use FiberPHP\Database\Event\BeforeFindEvent; use FiberPHP\Database\Event\AfterInsertEvent; use FiberPHP\Event\Event; // 监听 CRUD 事件(继承匹配自动覆盖父类/接口) Event::on(BeforeFindEvent::class, function (array $payload, BeforeFindEvent $event) { // $payload['query'] 是 Query 对象 }); // 可用事件类:BeforeFindEvent / BeforeSelectEvent / AfterInsertEvent / AfterUpdateEvent / AfterDeleteEvent / AfterConnectEvent // SQL 运行时:QueryEvent / SlowQueryEvent / QueryErrorEvent(目录:src/Event/) ``` **SQL 运行时事件(字符串 API,走 Db::listen 内部回调)**: ```php // SQL 查询 / 慢查询 / 错误 db()->listen('query', function (array $payload) { // payload: sql / bindings / duration_ms / connection }); db()->listen('slow', function (array $payload) { // payload: 同上,触发条件超 slow_sql_threshold }); db()->listen('error', function (array $payload) { // payload: sql / bindings / error / connection }); // 慢查询自动 EXPLAIN:超过 slow_sql_threshold 的 SELECT/WITH // 自动执行 EXPLAIN 并写 log_channel 通道(info 级,含执行计划) ``` 配合 `log_sql => true`,SQL 全量走 `log_channel => 'db'` 通道。 ## 扩展点 ```php // 注册驱动(方言继承 AbstractDriver) Db::registerDriver('mysql', MysqlDriver::class); // 注册 SQL 片段解析器(Builder 无状态扩展) $db->connect()->getBuilder()->registerParser('order', [ 'custom' => fn($q, $v) => '...', ]); // 自定义时间规则 $q->timeRule(['lunar_new_year' => fn() => ['2026-02-17 00:00:00', '2026-02-17 23:59:59']]); ``` `AbstractDriver` 关键方法:`connect / getFields / getTables / jsonPath / jsonContains / buildUpsertPrefix / quoteExcludedColumn / lock / random / getRealDriverName` 等。 ## 目录结构 ``` database/ ├── config/database.php # 配置样例 ├── src/ │ ├── Pool/ │ │ ├── AbstractPool.php # 连接池抽象基类 │ │ ├── SyncPool.php # 同步池(无协程环境) │ │ └── WorkermanPool.php # 协程池适配 │ ├── Driver/ │ │ ├── AbstractDriver.php # 驱动模板基类 │ │ ├── Mysql.php / Pgsql.php / Sqlite.php │ │ ├── SqlStandardDialect.php │ │ └── DriverProvider.php │ ├── Concern/ # Query/Connection 行为拆分 │ │ ├── WhereQuery / TimeFieldQuery / JoinQuery / Paginatable │ │ ├── ResultOperation / QueryCache / Cursor │ │ └── Crud / Transaction / Schema / Bindings │ ├── Exception/ # DbException / DataNotFoundException / DuplicateException / PDOException │ ├── Pagination/ # AbstractPaginator / Paginator / CursorPaginator │ ├── Builder.php # SQL 生成(无状态) │ ├── Connection.php # 连接 + 执行 + schema │ ├── Db.php # 管理器(多连接 + 池) │ ├── DbProvider.php # 容器注册 │ ├── LazyCollection.php # 惰性集合 │ ├── Query.php # 查询构造器 │ ├── Raw.php / Expression.php # 原生表达式 │ └── helpers.php # db() / raw() / inc() / dec() / paginate() 助手 └── tests/ # 单元测试 ``` ## 测试 ```bash composer install && php vendor/bin/phpunit ``` 集成测试默认跳过(需真实数据库),设置环境变量后启用:`DB_HOST` / `DB_DATABASE` / `DB_USERNAME` / `DB_PASSWORD`。