鸿蒙应用开发实战【36】— 数据查询优化:predicates 链式调用
鸿蒙应用开发实战【36】— 数据查询优化:predicates 链式调用
本文是「号码助手全栈开发系列」第 36 篇,持续更新中…
开源社区:https://openharmonycrossplatform.csdn.net
前言
在 DAO 层的基本 CRUD 中,RdbPredicates 承担了所有查询条件的构建工作。但 predicates 的能力远不止 equalTo 和 orderByAsc 这么简单——它支持链式调用、复杂条件组合(AND/OR/括号)、原生 SQL 表达式等高级功能。正确使用 predicates 可以写出更高效、更安全的查询代码。
本篇涵盖:predicates 链式调用模式、AND/OR 组合条件、beginWrap/endWrap 括号分组、聚合查询的替代方案、executeSql 原生 SQL、索引优化、查询性能分析与 explain 工具。

一、predicates 链式调用
每个 predicates 方法都返回自身,支持链式调用:
let predicates = new relationalStore.RdbPredicates('app_binding')
.equalTo('status', '使用中')
.orderByDesc('updated_at')
.limitAs(20);
等价于:
let predicates = new relationalStore.RdbPredicates('app_binding');
predicates.equalTo('status', '使用中');
predicates.orderByDesc('updated_at');
predicates.limitAs(20);
链式调用更紧凑,适合条件固定的场景。条件动态组合时,分布调用更合适。
1.1 predicates 常用方法一览
| 方法 | 功能 | 示例 |
|---|---|---|
equalTo(field, value) |
等值匹配 | equalTo('status', '使用中') |
notEqualTo(field, value) |
不等值匹配 | notEqualTo('status', '已停用') |
like(field, pattern) |
模糊匹配 | like('app_name', '%微信%') |
in(field, values) |
IN 集合匹配 | in('id', [1,2,3]) |
greaterThan(field, value) |
大于 | greaterThan('sort_order', 0) |
lessThan(field, value) |
小于 | lessThan('created_at', '2026-01-01') |
orderByAsc(field) |
升序 | orderByAsc('sort_order') |
orderByDesc(field) |
降序 | orderByDesc('updated_at') |
limitAs(count) |
限制条数 | limitAs(20) |
offsetAs(offset) |
偏移量 | offsetAs(0) |
beginWrap() |
开始括号分组 | 与 endWrap 配合 |
endWrap() |
结束括号分组 | 与 beginWrap 配合 |
and() |
AND 逻辑 | 默认连接符 |
or() |
OR 逻辑 | 切换为 OR |
二、AND / OR 条件组合
2.1 基本 AND 组合
predicates 默认是 AND 关系,多个条件自动 AND:
// WHERE status = '使用中' AND category = '支付'
let predicates = new relationalStore.RdbPredicates('app_binding');
predicates.equalTo('status', '使用中'); // AND
predicates.equalTo('category', '支付'); // AND
2.2 使用 OR
// WHERE status = '使用中' OR status = '待换绑'
let predicates = new relationalStore.RdbPredicates('app_binding');
predicates.equalTo('status', '使用中');
predicates.or();
predicates.equalTo('status', '待换绑');
2.3 AND 和 OR 混用
// WHERE status = '使用中' AND ( category = '支付' OR category = '金融' )
let predicates = new relationalStore.RdbPredicates('app_binding');
predicates.equalTo('status', '使用中');
predicates.and();
predicates.beginWrap();
predicates.equalTo('category', '支付');
predicates.or();
predicates.equalTo('category', '金融');
predicates.endWrap();
2.4 复杂筛选示例
首页筛选面板的组合条件:
/**
* 构建筛选 predicates
* 三个维度:分类、卡号、状态,彼此 AND,每个维度内的选项 OR
*/
function buildFilterPredicates(options: {
categories?: string[];
cardIds?: number[];
statuses?: string[];
}): relationalStore.RdbPredicates {
let predicates = new relationalStore.RdbPredicates('app_binding');
let hasPrev = false;
// 分类筛选:OR 关系
if (options.categories && options.categories.length > 0) {
if (hasPrev) predicates.and();
predicates.beginWrap();
options.categories.forEach((cat, i) => {
if (i > 0) predicates.or();
predicates.equalTo('category', cat);
});
predicates.endWrap();
hasPrev = true;
}
// 卡号筛选:OR 关系
if (options.cardIds && options.cardIds.length > 0) {
if (hasPrev) predicates.and();
predicates.in('card_id', options.cardIds);
hasPrev = true;
}
// 状态筛选:OR 关系
if (options.statuses && options.statuses.length > 0) {
if (hasPrev) predicates.and();
predicates.beginWrap();
options.statuses.forEach((status, i) => {
if (i > 0) predicates.or();
predicates.equalTo('status', status);
});
predicates.endWrap();
hasPrev = true;
}
predicates.orderByDesc('updated_at');
return predicates;
}
生成的 SQL:
WHERE category IN ('支付', '金融')
AND card_id IN (1, 2)
AND (status = '使用中' OR status = '待换绑')
ORDER BY updated_at DESC
三、查询优化策略
3.1 列投影(Columns)
只查询需要的字段,减少数据传输:
// ❌ 差:查询所有列
let rs = await store.query(predicates);
// ✅ 好:只查询需要的列
let rs = await store.query(predicates, [
'id', 'app_name', 'status', 'card_id'
]);
在需要查询 COUNT(*) 等聚合时,列投影尤其重要:
// 只查询 count
let rs = await store.query(predicates, ['COUNT(*) AS count']);
rs.goToFirstRow();
const total = rs.getLong(0);
3.2 分页查询
对于可能返回大量数据的查询,使用 limit + offset:
let predicates = new relationalStore.RdbPredicates('card');
predicates.orderByAsc('sort_order');
predicates.limitAs(20); // 每页 20 条
predicates.offsetAs(0); // 第 1 页
3.3 排序优化
多字段排序时,优先级从左到右:
// 先按状态排序(使用中排前面),再按更新时间倒序
predicates.orderByAsc('status');
predicates.orderByDesc('updated_at');
📌 排序与索引:如果
status和updated_at上有复合索引(status, updated_at),排序可以直接使用索引,避免文件排序。
四、索引设计
4.1 索引的原理
索引是数据库查询优化的核心武器。没有索引时,SQLite 执行全表扫描——逐行检查每条记录是否匹配条件。有索引时,通过 B-Tree 结构快速定位符合条件的数据行。
4.2 哪些字段需要索引
// 在 DatabaseService 中创建索引
const CREATE_INDEXES = `
-- app_binding 表
CREATE INDEX IF NOT EXISTS idx_binding_card_id ON app_binding(card_id);
CREATE INDEX IF NOT EXISTS idx_binding_status ON app_binding(status);
CREATE INDEX IF NOT EXISTS idx_binding_category ON app_binding(category);
-- card 表
CREATE INDEX IF NOT EXISTS idx_card_operator ON card(operator);
-- sms_candidate 表
CREATE INDEX IF NOT EXISTS idx_candidate_imported ON sms_candidate(imported);
CREATE INDEX IF NOT EXISTS idx_candidate_sms_body ON sms_candidate(sms_body);
`;
索引策略:
| 表 | 索引字段 | 查询场景 |
|---|---|---|
| app_binding | card_id |
WHERE card_id = ?(查某卡号的绑定) |
| app_binding | status |
WHERE status = ‘使用中’(筛选状态) |
| app_binding | category |
WHERE category = ‘支付’(分类筛选) |
| card | operator |
WHERE operator = ‘中国移动’ |
| sms_candidate | imported |
WHERE imported = 0(查询未导入) |
| sms_candidate | sms_body |
去重查询优化 |
4.3 复合索引
对于同时按多个字段筛选的场景,复合索引更高效:
-- 按状态 + 更新时间排序
CREATE INDEX IF NOT EXISTS idx_binding_status_updated
ON app_binding(status, updated_at DESC);
复合索引 (status, updated_at) 可以同时服务于:
WHERE status = '使用中' ORDER BY updated_at DESCWHERE status = '使用中'- 但不能服务于仅查询
updated_at的条件
📌 最左前缀原则:复合索引
(A, B, C)可以服务A、(A, B)、(A, B, C)的查询,但不能单独服务B或C。
五、executeSql 原生查询
当 predicates 无法表达某些查询时(如 JOIN、子查询、复杂聚合),使用 executeSql 执行原生 SQL。
5.1 原生 SQL vs predicates 对比
| 场景 | predicates | executeSql |
|---|---|---|
| 等值查询 | ✅ equalTo | ✅ WHERE = |
| 模糊搜索 | ✅ like | ✅ LIKE |
| IN 查询 | ✅ in | ✅ IN |
| JOIN 查询 | ❌ 不支持 | ✅ JOIN |
| GROUP BY | ❌ 不支持 | ✅ GROUP BY |
| 子查询 | ❌ 不支持 | ✅ 子查询 |
| 窗口函数 | ❌ 不支持 | ✅ 窗口函数 |
| SQL 注入风险 | ✅ 无 | ⚠️ 需谨慎 |
5.2 使用 executeSql 进行 JOIN
async queryWithJoin(): Promise<AppRow[]> {
const sql = `
SELECT
b.id, b.app_name, b.category, b.card_id,
b.status, b.remark, b.icon_key,
b.created_at, b.updated_at,
c.label AS card_label,
c.color AS card_color,
c.phone
FROM app_binding b
LEFT JOIN card c ON b.card_id = c.id
WHERE b.status IN ('使用中', '待换绑')
ORDER BY b.updated_at DESC
LIMIT 50
`;
// executeSql 不返回 ResultSet
// 需要使用 RdbStore 的 query 方法配合 predicates 无法实现
// 或者直接操作 SQLite 原生接口
await this.store.executeSql(sql);
// 返回值为 void,此处仅为概念展示
// 实际项目中可采用批量查询 + 内存组合方案
return await this.listWithCard();
}
5.3 使用 executeSql 进行复杂聚合
async countByCategory(): Promise<{ category: string; count: number }[]> {
// 使用 predicates + 列投影模拟 GROUP BY
// 由于 predicates 不支持 GROUP BY,在应用层聚合
const all = await this.listAll();
const map = new Map<string, number>();
for (const item of all) {
const category = item.category || '未分类';
map.set(category, (map.get(category) || 0) + 1);
}
return Array.from(map.entries())
.map(([category, count]) => ({ category, count }))
.sort((a, b) => b.count - a.count);
}
六、查询性能分析
6.1 使用执行计划分析查询
// 通过 EXPLAIN 分析查询计划
// 在 executeSql 中执行
const explainSql = 'EXPLAIN QUERY PLAN SELECT * FROM app_binding WHERE status = ?';
// 分析输出可以帮助判断是否使用了索引
EXPLAIN 输出的典型解读:
| 输出 | 含义 | 操作 |
|---|---|---|
SCAN TABLE |
全表扫描 | 需要加索引 |
SEARCH TABLE USING INDEX |
使用了索引 | 查询高效 |
USE TEMP B-TREE FOR ORDER BY |
排序未使用索引 | 需加复合索引 |
SEARCH USING COVERING INDEX |
覆盖索引 | 最高效 |
6.2 慢查询定位
// 使用 hilog 记录查询耗时
async queryWithTiming(predicates: relationalStore.RdbPredicates): Promise<CardEntity[]> {
const start = performance.now();
let rs = await this.store.query(predicates);
try {
let list: CardEntity[] = [];
while (rs.goToNextRow()) {
list.push(parseCard(rs));
}
const elapsed = performance.now() - start;
if (elapsed > 100) {
console.warn(`Slow query: ${elapsed}ms, rows=${list.length}`);
}
return list;
} finally {
rs.close();
}
}
七、最佳实践总结
7.1 查询优化清单
- ✅ 常用查询字段建索引
- ✅ WHERE 条件用索引字段
- ✅ 只查需要的列(列投影)
- ✅ 查询大量数据加分页
- ✅ ORDER BY 字段靠近 WHERE 字段组成复合索引
- ✅ predicates 实现了 AND/OR/括号,无需回退到原生 SQL
- ✅ LIKE 尽量少用前缀通配(少用
%keyword,多用keyword%)
7.2 避免的反模式
// ❌ SELECT * — 查了不需要的列
let rs = await store.query(predicates);
// ✅ SELECT id, app_name, status
let rs = await store.query(predicates, ['id', 'app_name', 'status']);
// ❌ 在循环中查数据库(N+1 问题)
for (const id of ids) {
const item = await dao.getById(id);
}
// ✅ 用 IN 一次查完
const items = await dao.listByIds(ids);
小结
本篇全面讲解了 RDB 查询优化的核心技能:
| 技能 | 方法 | 关键点 |
|---|---|---|
| 链式调用 | 方法返回自身 | 固定条件用链式,动态组合用分布 |
| 条件组合 | beginWrap/endWrap | AND/OR + 括号分组 |
| 列投影 | columns 参数 | 只查需要的字段 |
| 分页 | limitAs + offsetAs | 避免大数据量 OOM |
| 索引 | CREATE INDEX | 常用查询字段建索引 |
| 复合索引 | (A, B, C) | 最左前缀原则 |
| 执行计划 | EXPLAIN QUERY PLAN | 验证索引是否生效 |
| 慢查询 | 性能计时 | >100ms 告警 |
下一篇将专题讲解「数据统计 countByStatus 聚合查询」,实现首页统计卡片的真实数据驱动。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- openHarmony 跨平台社区:https://openharmonycrossplatform.csdn.net
- HarmonyOS 官方文档:https://developer.huawei.com/consumer/cn/doc/
- HarmonyOS RDB开发指南:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/data-persistence-by-rdb
- HarmonyOS hilog日志:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/hilog
更多推荐


所有评论(0)