查询构造器

查询构造器(Query Builder)提供链式 API 构建并执行参数化 SQL,是 Db::table() 的返回对象(Viswoole\Database\BaseQuery)。所有值经参数绑定写入数据库,条件运算符与联表运算符均做白名单校验,防止 SQL 注入。

本文依据框架源码 src/Database/BaseQuery.phpsrc/Database/Query/{Crud,Where,WhereGroup,Join,Options}.phpsrc/Database/Facade/Db.php 编写。

获取查询构造器

php
use Viswoole\Database\Facade\Db;

// 默认通道
Db::table('user');               // 第二参数可指定主键名,默认 'id'

// 指定通道
Db::channel('order')->table('order');

查询执行后构造器会自动重置条件并复用实例;如需在保留原实例的前提下构建新查询,调用 newQuery() 克隆一个全新实例。

查询数据

php
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') 支持两种写法:

php
// 两参简写:默认 = 运算符;值传数组时自动转为 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
  • 集合运算符:INNOT IN
  • 区间运算符:BETWEENNOT BETWEEN
  • 空值判断:IS NULLIS 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)批量设置条件,见下文
php
// 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() 一致:

php
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

php
// 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 子查询

构造选项

字段与别名

php
// 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 有效)

排序、分组与分页

php
// 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

php
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(stringRaw $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)求和
php
$total    = Db::table('user')->where('status', 1)->count();
$totalSum = Db::table('order')->sum('amount');

聚合字段校验

聚合字段仅允许字母、数字、下划线与点号(或 *),防止用户输入被拼入聚合表达式造成注入。

写入与删除

php
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()、字段自增等数据库端表达式。

大结果集

php
// 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 开启结果缓存:命中直接返回缓存,未命中则查询后写入;执行写入操作时自动清除对应缓存。

参数类型默认值说明
keystring必填缓存键
expireint0有效期(秒),0 表示永不过期
tagstring | nullnull缓存标签,便于按组清理
storestring | nullnull缓存存储器名称,null 使用默认存储器
php
$users = Db::table('user')
    ->where('status', 1)
    ->cache('user_list', 600, 'user')   // 10 分钟,标签 user
    ->select();

缓存依赖

缓存由缓存组件驱动,存储器与标签的配置见 缓存cursor()chunk() 会强制关闭缓存。

事务

php
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 表示无事务。
php
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 事务

原生查询

php
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()

下一步