欢迎光临
我们一直在努力

鸿蒙应用开发实战【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 DESC
  • WHERE status = '使用中'
  • 但不能服务于仅查询 updated_at 的条件

📌 最左前缀原则:复合索引 (A, B, C) 可以服务 A、(A, B)、(A, B, C) 的查询,但不能单独服务 B 或 C。


五、executeSql 原生查询

当 predicates 无法表达某些查询时(如 JOIN、子查询、复杂聚合),使用 executeSql 执行原生 SQL。

5.1 原生 SQL vs predicates 对比

场景predicatesexecuteSql
等值查询 ✅ 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)
    未经允许不得转载:171主机测评 » 鸿蒙应用开发实战【36】— 数据查询优化:predicates 链式调用
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址