查询构造器
查询构造器(Query Builder)提供链式 API 构建并执行参数化 SQL,是 Db::table() 的返回对象(Viswoole\Database\BaseQuery)。所有值经参数绑定写入数据库,条件运算符与联表运算符均做白名单校验,防止 SQL 注入。
本文依据框架源码
src/Database/BaseQuery.php、src/Database/Query/{Crud,Where,WhereGroup,Join,Options}.php与src/Database/Facade/Db.php编写。
获取查询构造器
use Viswoole\Database\Facade\Db;
// 默认通道
Db::table('user'); // 第二参数可指定主键名,默认 'id'
// 指定通道
Db::channel('order')->table('order');查询执行后构造器会自动重置条件并复用实例;如需在保留原实例的前提下构建新查询,调用 newQuery() 克隆一个全新实例。
查询数据
use Viswoole\Database\Facade\Db;
// 主键查询,返回 DataSet 单行数据集;未查到返回空 DataSet(用 isEmpty() 判断)
$user = Db::table('user')->find(1);
// find 第二参数传 false:未查到时抛出 DataNotFoundException
$user = Db::table('user')->find(1, false);
// 多条查询,返回 Collection 集合
$list = Db::table('user')->where('status', 1)->select();
// select(false) / get(false):结果为空时抛出 DataNotFoundException
// get() 是 select() 的别名;getArray() 以纯数组返回结果
$array = Db::table('user')->where('status', 1)->getArray();
// value():取第一行指定列的值(自动 LIMIT 1),无结果返回 false
$name = Db::table('user')->where('id', 1)->value('name');查询结果为 Collection(多行)与 DataSet(单行)集合对象,支持数组式访问、toArray()、filter()、sortBy() 等操作,详见 ORM 模型 — 查询结果。
条件查询
where 系列
where(string $column, string|int|float|array $operator, string|int|float|array|null $value = null, string $connector = 'AND') 支持两种写法:
// 两参简写:默认 = 运算符;值传数组时自动转为 IN
->where('status', 1)
->where('type', [1, 2]) // 等价 type IN (1, 2)
// 三参写法:显式指定运算符
->where('age', '>=', 18)
// 第四参数指定连接符 AND | OR
->where('age', '>=', 18, 'OR')条件运算符白名单(Where::OPERATORS),传入其他运算符抛出 InvalidArgumentException:
- 比较运算符:
=、!=、<>、>、>=、<、<=、LIKE - 集合运算符:
IN、NOT IN - 区间运算符:
BETWEEN、NOT BETWEEN - 空值判断:
IS NULL、IS NOT NULL
其余条件方法(均支持末位 $connector = 'AND' 参数):
| 方法 | 说明 |
|---|---|
orWhere($column, $operator, $value = null) | OR 连接的条件(andWhere() 为显式 AND) |
whereIn($column, array $value) | IN 条件,空数组抛异常 |
whereNotIn($column, array $value) | NOT IN 条件,空数组抛异常 |
whereNull($column) | IS NULL |
whereNotNull($column) | IS NOT NULL |
whereBetween($column, array $value) | BETWEEN 区间(两个元素) |
whereNotBetween($column, array $value) | NOT BETWEEN 区间 |
wheres(array $wheres) | 批量设置条件,见下文 |
// wheres():键值对默认 = / 数组默认 IN;
// 索引数组形式 ['字段', '运算符', '值', '连接符?'] 支持任意白名单运算符
->wheres([
'status' => 1,
'type' => [1, 2],
['age', '>=', 18],
['role', '=', 'admin', 'OR'],
])whereGroup 条件分组
whereGroup(array $wheres, string $connector = 'AND') 将一组条件用括号包裹,构建 (A AND B) OR C 等复杂逻辑,组内条件格式与 wheres() 一致:
Db::table('user')
->where('type', 'vip')
->whereGroup([
['status', '=', 1],
['role', '=', 'admin', 'OR'],
['role', '=', 'super_admin', 'OR'],
], 'AND')
->select();
// WHERE type = 'vip' AND (status = 1 AND role = 'admin' OR role = 'super_admin')原生条件与 EXISTS
// whereRaw(string $sql, array $bindings = []):原生片段,占位符参数绑定
->whereRaw('DATE(create_time) = ?', ['2026-01-01'])
// whereExists(string $sql, array $bindings = []):EXISTS 子查询
->whereExists('SELECT 1 FROM orders WHERE orders.user_id = user.id')
// whereNotExists(...):NOT EXISTS 子查询构造选项
字段与别名
// columns(string ...$column):查询字段,支持 "列 AS 别名";不传则查全部
->columns('id', 'name', 'COUNT(*) AS total')
// withoutColumns(string ...$column):排除字段
->withoutColumns('password', 'pay_password')
// alias(string $alias):表别名;join 多表时配合别名区分字段
Db::table('user')->alias('u')->columns('u.id', 'u.name')->select();
// distinct(bool $flag = true):DISTINCT 去重
// force(string $index):强制索引 FORCE INDEX(仅 MySQL / SQLite 有效)排序、分组与分页
// orderBy:三种形式
->orderBy('create_time', 'desc') // 单列
->orderBy(['sort' => 'asc', 'age' => 'desc']) // 多列各自方向
->orderBy(Db::raw('RAND()')) // 原生表达式
// groupBy(string|array $columns):分组,字符串可逗号分隔
->groupBy('user_id')
// having(string $column, string $operator, mixed $value, string $connector = 'AND')
// 运算符白名单:= != <> > >= < <= LIKE
->having('order_count', '>', 5)
// page(int $page, int $pageSize):分页(页码从 1 开始),等价 offset + limit
->page(2, 20)
// limit(int) / offset(int):底层方法联表查询
join(string $table, string $localKey, string $foreignKey, string $operator = '=', string $type = 'INNER'),类型支持 INNER / LEFT / RIGHT / FULL;另有 leftJoin()、rightJoin()、fullJoin() 快捷方法。ON 条件运算符白名单:= != <> > >= < <= LIKE。
Db::table('user u')
->leftJoin('order o', 'u.id', 'o.user_id') // 注意第 3 参是外键、第 4 参是运算符
->columns('u.name', 'o.amount')
->select();leftJoin 参数顺序已变更
leftJoin 签名为 (table, localKey, foreignKey, operator)。若第 3 个参数收到运算符(旧版参数顺序 (table, localKey, operator, foreignKey) 的写法),框架会直接抛出带提示的异常,请按新顺序调整调用。
其他选项
| 方法 | 说明 |
|---|---|
strict(bool $flag = true) | 严格模式:写入不存在的字段抛异常;关闭则忽略 |
| `union(string | Raw $query, string $type = ‘UNION’)` |
lockForUpdate() | 排他锁 FOR UPDATE,需在事务内使用 |
sharedLock() | 共享锁 LOCK IN SHARE MODE |
replace(bool $flag = true) | 写入改用 REPLACE INTO(仅 MySQL) |
toRaw() | 仅生成 SQL 不执行,返回 Raw 对象(调试 SQL 用) |
聚合查询
| 方法 | 签名 | 说明 |
|---|---|---|
count() | count(string $column = '*') | 计数 |
min() | min(string $column) | 最小值 |
max() | max(string $column) | 最大值 |
avg() | avg(string $column) | 平均值 |
sum() | sum(string $column) | 求和 |
$total = Db::table('user')->where('status', 1)->count();
$totalSum = Db::table('order')->sum('amount');聚合字段校验
聚合字段仅允许字母、数字、下划线与点号(或 *),防止用户输入被拼入聚合表达式造成注入。
写入与删除
use Viswoole\Database\Facade\Db;
// insert:关联数组单条写入,索引数组批量写入;返回受影响行数
Db::table('user')->insert(['name' => '张三', 'status' => 1]);
// insertGetId:仅接受关联数组,返回自增主键值
$id = Db::table('user')->insertGetId(['name' => '张三']);
// update:返回受影响行数;值支持 Raw 表达式
Db::table('user')->where('id', 1)->update([
'login_count' => Db::raw('login_count + 1'),
]);
// delete:配合 where 条件删除,返回受影响行数
Db::table('user')->whereIn('id', [10, 11])->delete();Db::raw(string $sql, array $bindings = []) 创建原生 SQL 表达式,值不会被参数绑定转义,适合 NOW()、字段自增等数据库端表达式。
大结果集
// cursor():游标查询,逐条返回 DataSet 的生成器,内存占用恒定
foreach (Db::table('user')->cursor() as $user) {
// 处理单条记录
}
// chunk(int $size):每批查询 $size 条,逐批产出 Collection 的生成器
foreach (Db::table('user')->chunk(1000) as $collection) {
// 处理一批记录
}使用场景
两者均绕过查询缓存、自动翻页。一次性处理十万级以上数据时,避免用 select() 把全量结果载入内存。
查询缓存
cache(string $key, int $expire = 0, ?string $tag = null, ?string $store = null) 为 SELECT 开启结果缓存:命中直接返回缓存,未命中则查询后写入;执行写入操作时自动清除对应缓存。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| key | string | 必填 | 缓存键 |
| expire | int | 0 | 有效期(秒),0 表示永不过期 |
| tag | string | null | null | 缓存标签,便于按组清理 |
| store | string | null | null | 缓存存储器名称,null 使用默认存储器 |
$users = Db::table('user')
->where('status', 1)
->cache('user_list', 600, 'user') // 10 分钟,标签 user
->select();缓存依赖
缓存由缓存组件驱动,存储器与标签的配置见 缓存。cursor() 与 chunk() 会强制关闭缓存。
事务
use Viswoole\Database\Facade\Db;
// 方式一(推荐):闭包事务,成功自动 commit,异常自动 rollback 并重抛,返回闭包返回值
$newId = Db::startTransaction(function () {
$id = Db::table('user')->insertGetId(['name' => '张三']);
Db::table('log')->insert(['action' => 'create_user', 'user_id' => $id]);
return $id;
});
// 方式二:手动管理
Db::startTransaction(); // 开启事务(别名 Db::start())
try {
Db::table('user')->insert(['name' => '张三']);
Db::commit();
} catch (\Throwable $e) {
Db::rollBack();
throw $e;
}嵌套事务
同一协程内事务支持多层嵌套,内层基于数据库 SAVEPOINT 实现:
commit()/rollBack()始终作用于当前最内层事务,需按后开先关(LIFO)顺序收尾;- 内层回滚仅回滚到该层保存点,外层操作不受影响;内层提交仅释放保存点,数据仍受最外层事务保护;
Db::transactionLevel(): int返回当前嵌套层级,0 表示无事务。
Db::startTransaction(function () { // 外层
Db::table('user')->insert(['name' => '张三']);
try {
Db::startTransaction(function () { // 内层(SAVEPOINT)
throw new \RuntimeException('失败');
});
} catch (\RuntimeException) {
// 仅内层回滚,外层事务可继续
}
Db::table('log')->insert(['action' => 'ok']);
});事务边界
事务基于协程上下文隔离,不同协程各自开启的事务互不影响。跨通道/跨库事务不保证原子性(无两阶段提交),需要跨库原子性的业务请使用 XA 事务。
原生查询
use Viswoole\Database\Facade\Db;
// query:执行查询语句,返回关联数组结果集;$master 为 true 强制从主库读取
$users = Db::query('SELECT * FROM user WHERE status = ?', [1]);
// execute:执行写入语句,返回受影响行数;传字段名时返回自增 ID
$count = Db::execute('UPDATE user SET status = ? WHERE id = ?', [0, 1]);
$newId = Db::execute('INSERT INTO user (name) VALUES (?)', ['张三'], 'id');query() 仅接受查询语句
query() 只接受 SELECT / SHOW / EXPLAIN / DESCRIBE / DESC / WITH / PRAGMA 开头的语句,写入语句会抛出 DbException,请改用 execute()。
