# 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-09-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # fiberphp/database 数据库核心包 FiberPHP 数据库组件:协程连接池 + 查询构造器 + SQL 生成 + 驱动方言抽象。内置 MySQL / PostgreSQL / SQLite 三驱动。 ## 特性 - **连接池**:workerman 协程环境自动池化(挂起等待/心跳探活/上限控制);无 workerman 时回退内置 `SyncPool`(同步复用),可脱离框架独立使用 - **驱动方言抽象**:方言差异收敛到 `DriverInterface`(17 方法),SQL 生成为无状态 Builder(可注册扩展解析器) - **查询缓存 / 延迟写入 / JSON 字段 / 游标流式读取 / 光标分页** - **慢查询诊断**:超阈值 SELECT 自动 EXPLAIN 并写日志;`query` 事件可监听 - **表结构缓存**:schema 元信息三层递进(运行时 → cache 包 → 数据库),支持按表清理 ## 环境要求 - PHP >= 8.3、ext-pdo - 可选:workerman/workerman(协程池,缺失时回退内置 SyncPool) - fiberphp/cache 为硬依赖(查询缓存 / 延迟写入 / 表结构缓存均通过其 `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()->newQuery('log')->table('access_log')->find(); ``` ## API 全集 ### 表与别名 ```php $q->table('user u') // 原始表名(含前缀需完整写) $q->name('user') // 自动加前缀(推荐) $q->name('user')->alias('u') // 别名 $q->name('user')->tableRaw('user FORCE INDEX(idx)') // 原生表表达式 $q->setTableSuffix('_2026') // 表名后缀(分表) $q->getTable() // 当前表名 ``` ### 字段选择 ```php $q->field('id,name') // 字符串 $q->field(['id', 'name', 'SUM(score)' => 'total']) // 数组(别名) $q->field(true) // 全部字段(展开为显式列表) $q->withoutField(['password']) // 排除字段 $q->fieldRaw('COUNT(*) AS c') // 原生字段 $q->distinct() // DISTINCT ``` ### 条件(WhereQuery) ```php $q->where('id', 1) // = $q->where('id', '>', 10) // 三参:运算符(=,<>,<,>,<=,>=,LIKE,IN,BETWEEN,...) $q->where(['status' => 1, 'type' => 'a']) // 数组(AND) $q->where(fn($q) => $q->where('a', 1)->whereOr('b', 2)) // 闭包子条件(括号分组) $q->whereOr('status', 2) // OR $q->whereNull('deleted_at') / whereNotNull() $q->whereIn('id', [1,2,3]) / whereNotIn() $q->whereBetween('age', [18, 30]) / whereNotBetween() $q->whereLike('name', '%tom%') / whereNotLike() $q->whereExists(fn($q) => ...) / whereNotExists() $q->whereColumn('score', '>', 'base_score') // 字段与字段比较 $q->whereExp('age', 'age + 1 > 20', $bind) // 表达式(带绑定) $q->whereRaw('YEAR(created_at) = ?', [2026]) // 原生 AND / whereOrRaw 原生 OR $q->whereFieldRaw('created_at', '>', 'NOW()') // 字段侧原生值 $q->whereFindInSet('tags', 'php') // FIND_IN_SET(MySQL) $q->whereJsonContains('ext->tags', 'php') // JSON 包含(三方言适配,OR 用 whereOr(fn) 包裹) // 条件触发(动态查询) $q->when($kw, fn($q) => $q->whereLike('name', "%$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', 'hour', 2) // 最近 N 小时 $q->timeRule(['today' => fn() => [...]]) // 自定义时间规则 ``` ### 排序 / 分组 / 技巧 ```php $q->order('id', 'desc') $q->order(['status' => 'asc', 'id' => 'desc']) $q->orderRaw('FIELD(id, 3,1,2)') $q->orderField('status', [2, 1, 3]) // 按值枚举排序 $q->orderRand() $q->group('type')->having('COUNT(*) > 5') $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->save(['id' => 1, 'age' => 20]) // 有主键更新,无主键插入 // 删 $q->delete(1) // 主键 $q->where('status', 0)->delete() // 原生 $q->execute('UPDATE user SET age = age + 1 WHERE id > ?', [100]) // int 影响行数 $q->query('SELECT * FROM user LIMIT 10') // array(连接层快捷) // 调试 $q->fetchSql(true)->find() // 返回 SQL 字符串而不执行 $q->getLastSql() // 最后执行的 SQL $q->buildSql() // 构造 SQL(子查询用) ``` ### 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->orderBy('id')->cursorPaginate(20); // CursorPaginator(keyset,大表友好) $paginator->items() / currentPage() / lastPage() / total() / hasMorePages() ``` ### 事务 ```php // 回调式(推荐) db()->transaction(function () { db()->name('user')->insert($row); db()->name('log')->insert($log); }); // 任一异常自动回滚并上抛 // 手动事务:经连接操作 $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 随查询自动登记 ``` ### 魔术快捷(__call) ```php $q->getByName('tom') // where('name','=','tom')->find() $q->getFieldById(5, 'name') // where('id','=',5)->value('name') $q->whereName('tom')->find() // where('name','tom') 链式快捷 ``` ## 连接与池 ```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 类,走 `fiberphp/event`)**: ```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 // 目录:src/Event/*.php ``` **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 // 注册驱动(方言实现 DriverInterface) 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']]); ``` `DriverInterface` 关键方法:`connect / getFields / getTables / jsonPath / jsonContains / buildUpsertPrefix / quoteExcludedColumn / lock / random / getRealDriverName` 等。 ## 目录结构 ``` database/ ├── config/database.php # 配置样例 ├── src/ │ ├── Contract/ │ │ ├── DriverInterface.php # 驱动方言契约 │ │ └── PoolInterface.php # 连接池契约 │ ├── Pool/ │ │ ├── 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 # 容器注册 │ ├── Facade/Db.php # Db Facade │ ├── LazyCollection.php # 惰性集合 │ ├── Query.php # 查询构造器 │ ├── Raw.php / Expression.php # 原生表达式 │ ├── Install.php # 安装钩子(发布 config/database.php) │ └── helpers.php # db() / raw() / inc() / dec() / paginate() 助手 └── tests/ # 单元测试 ``` ## 测试 ```bash composer install && php vendor/bin/phpunit ``` 集成测试默认跳过(需真实数据库),设置环境变量后启用:`DB_HOST` / `DB_DATABASE` / `DB_USERNAME` / `DB_PASSWORD`。