当前位置: 首页 > news >正文

【MongoDB】查询条件运算符:$expr 和 $regex 详解,以及为什么$where和$expr难以使用索引

一、$expr 运算符

$expr 允许你在查询语句中使用聚合表达式,实现字段间的比较和复杂条件查询。

注解$expr 主要用于比较同一文档中不同字段的值,或者使用聚合表达式进行条件判断。

基本语法

db.collection.find({$expr: {<聚合表达式>}
})

常见使用场景

1. 比较同一文档中的两个字段
// 查找库存量小于销售量的产品
db.products.find({$expr: { $lt: ["$stock", "$sales"] }
})
2. 使用条件逻辑
// 查找折扣价大于原价50%的产品
db.products.find({$expr: { $gt: ["$discountPrice", { $multiply: ["$price", 0.5] }] }
})

$expr 常用操作符

操作符描述示例
$eq等于$eq: ["$field1", "$field2"]
$ne不等于$ne: ["$field1", "value"]
$gt大于$gt: ["$field1", 100]
$lt小于$lt: ["$field1", "$field2"]
$and逻辑与$and: [expr1, expr2]
$or逻辑或$or: [expr1, expr2]
$concat字符串连接$concat: ["$fname", " ", "$lname"]
$substr字符串截取$substr: ["$name", 0, 3]

注意:使用 $expr 时,字段名前需要加 $ 符号,表示引用字段值而非字面量。

二、$regex 运算符

$regex 提供正则表达式功能,用于模式匹配字符串查询。

注解:正则表达式是强大的文本匹配工具,$regex 让你能在 MongoDB 中利用这一功能。

基本语法

db.collection.find({field: { $regex: /pattern/, $options: 'options' }
})// 或者
db.collection.find({field: { $regex: 'pattern', $options: 'options' }
})

常用选项($options)

选项描述
i不区分大小写
m多行匹配
x忽略空白字符
s允许点字符(.)匹配所有字符

使用示例

1. 简单匹配
// 查找名字以"Jo"开头的用户(不区分大小写)
db.users.find({name: { $regex: /^Jo/i }
})
2. 包含特定模式
// 查找邮箱包含"example.com"或"sample.com"的用户
db.users.find({email: { $regex: /(example|sample)\.com/ }
})
3. 使用字符串而非正则字面量
// 查找描述中包含"mongodb"的产品(不区分大小写)
db.products.find({description: { $regex: "mongodb", $options: 'i' }
})

性能考虑

重要提示:正则表达式查询通常无法使用索引,尤其是:

  • 当使用通配符开头(如 /^abc/ 可以使用索引,但 /abc$//.*abc/ 则不行)
  • 使用复杂正则表达式时

三、$expr 和 $regex 结合使用

你可以组合这两个运算符实现更复杂的查询:

// 查找全名(firstname + lastname)包含"Smith"且邮箱与用户名相同的用户
db.users.find({$expr: {$and: [{ $regexMatch: { input: { $concat: ["$firstname", " ", "$lastname"] }, regex: "Smith" } },{ $eq: ["$email", "$username"] }]}
})

注意:MongoDB 4.2+ 引入了 $regexMatch 等聚合运算符,使正则表达式在 $expr 中使用更方便。

四、总结对比

特性$expr$regex
主要用途字段间比较和复杂逻辑文本模式匹配
语法使用聚合表达式使用正则表达式
性能可以使用索引(取决于表达式)有限索引支持
版本需要 MongoDB 3.6+所有版本支持
典型场景比较同一文档中的多个字段搜索、模糊匹配

五、最佳实践建议

  1. 谨慎使用 $expr

    • 对于简单查询,优先使用常规查询操作符
    • 只在需要字段间比较或复杂逻辑时使用 $expr
  2. 优化 $regex 查询

    • 避免前导通配符(如 ^ 开头可以使用索引)
    • 考虑使用文本索引替代复杂正则表达式
    • 对常用模式考虑预计算字段
  3. 测试查询性能

    • 使用 explain() 方法分析查询执行计划
    • 对大集合进行性能测试

通过案例学习$expr用法

一、金融领域:风险交易检测

场景:检测异常大额交易(单笔超过账户日均余额30%的交易)

db.transactions.find({$expr: {$and: [// 确保amount和dailyBalance字段存在{ $ifNull: ["$amount", false] },{ $ifNull: ["$dailyBalance", false] },// 主条件:交易金额 > 日均余额的30%{ $gt: ["$amount",{ $multiply: ["$dailyBalance", 0.3] } // 计算日均余额的30%] },// 附加条件:交易时间在非工作时间(晚上8点到早上6点){$or: [{ $lt: [{ $hour: "$transactionTime" }, 6] },{ $gt: [{ $hour: "$transactionTime" }, 20] }]}]}
}).sort({ amount: -1 }).limit(100)

业务注释

  1. $ifNull 确保字段存在,避免空值报错
  2. $multiply 实现金额百分比计算
  3. $hour + $or 组合实现时间段过滤
  4. 最后排序并限制结果数量,便于风险团队优先处理大额交易

二、电商领域:动态定价监控

场景:找出定价低于成本价或异常折扣的商品(当前价<成本价 或 折扣>70%)

db.products.find({$expr: {$or: [// 情况1:当前售价低于成本价{ $lt: ["$currentPrice", "$costPrice"] },// 情况2:折扣力度超过70%(需先计算折扣率){$gt: [{ $divide: [{ $subtract: ["$originalPrice", "$currentPrice"] },"$originalPrice"]},0.7]}]},status: "active" // 只查询上架商品
})

业务注释

  1. $subtract 计算原价与现价的差额
  2. $divide 计算折扣百分比
  3. 组合使用 $or 覆盖两种异常情况
  4. 额外添加常规查询条件(status)与 $expr 配合使用

三、物流领域:时效性分析

场景:查找实际配送时间超过承诺时间2倍以上的订单

db.orders.aggregate([{$match: {$expr: {$and: [// 确保必要字段存在且已完成配送{ $gt: ["$actualDeliveryDate", null] },{ $gt: ["$promisedDeliveryDate", null] },{ $eq: ["$status", "delivered"] },// 计算时间差(毫秒){$gt: [{ $subtract: ["$actualDeliveryDate", "$orderDate"] },{ $multiply: [{ $subtract: ["$promisedDeliveryDate", "$orderDate"] },2]}]}]}}},{$project: {// 计算超时天数(展示用)delayDays: {$divide: [{$subtract: [{ $subtract: ["$actualDeliveryDate", "$orderDate"] },{ $subtract: ["$promisedDeliveryDate", "$orderDate"] }]},1000 * 60 * 60 * 24 // 毫秒转天数]},orderId: 1,customerId: 1}}
])

业务注释

  1. 使用聚合管道结合 $match$expr 实现复杂过滤
  2. 日期字段比较需转换为毫秒数计算
  3. $project 阶段将毫秒差转换为易读的天数
  4. 多层嵌套的表达式展示 MongoDB 强大的计算能力

四、人力资源:薪资合规审计

场景:检测薪资异常(当前薪资<入职薪资 或 涨幅超过职级上限)

db.employees.find({$expr: {$or: [// 异常情况1:当前薪资低于入职薪资{ $lt: ["$currentSalary", "$startingSalary"] },// 异常情况2:薪资涨幅超过职级允许上限{$let: {vars: {// 计算实际涨幅百分比actualIncrease: {$divide: [{ $subtract: ["$currentSalary", "$startingSalary"] },"$startingSalary"]},// 获取该职级的允许最大涨幅maxAllowed: {$switch: {branches: [{ case: { $eq: ["$grade", "P1"] }, then: 0.3 },{ case: { $eq: ["$grade", "P2"] }, then: 0.4 },{ case: { $eq: ["$grade", "P3"] }, then: 0.5 }],default: 0.2}}},in: {$gt: ["$$actualIncrease", "$$maxAllowed"]}}}]},department: { $in: ["Engineering", "Product"] } // 只检查特定部门
})

业务注释

  1. 使用 $let 定义临时变量简化复杂表达式
  2. $switch 实现职级与薪资上限的映射
  3. 多层嵌套的算术运算演示 MongoDB 的表达能力
  4. 最终与常规查询条件组合使用

五、物联网(IoT):设备异常监测

场景:找出传感器读数异常的设备(当前值超过3个标准差)

db.deviceReadings.find({$expr: {$gt: ["$currentValue",{$add: ["$avgValue",{ $multiply: ["$stdDev", 3] } // 计算3个标准差范围]}]},$where: "new Date() - this.lastMaintenanceDate > 1000*60*60*24*30" // 超过30天未维护
})

业务注释

  1. 统计学方法应用于设备监测(平均值+3标准差)
  2. $add$multiply 组合计算阈值
  3. 结合 $where 实现更复杂的脚本判断(注意性能影响)
  4. 适合大规模IoT设备的异常检测场景

六、进阶技巧:性能优化方案

优化建议1:为 $expr 常用字段组合创建复合索引

// 为物流案例创建优化索引
db.orders.createIndex({status: 1,actualDeliveryDate: 1,promisedDeliveryDate: 1
})// 为金融交易案例创建优化索引
db.transactions.createIndex({transactionTime: 1,amount: -1
})

优化建议2:使用 $redact 替代复杂 $expr

// 人力资源案例的替代方案
db.employees.aggregate([{$redact: {$cond: {if: {$or: [{ $lt: ["$currentSalary", "$startingSalary"] },{ $gt: ["$salaryIncreaseRatio", "$grade.maxAllowedRatio"] }]},then: "$$KEEP",else: "$$PRUNE"}}}
])

一、为什么$where$expr难以使用索引?

1. $where:使用JavaScript表达式查询

$where允许通过JavaScript代码定义查询条件(例如判断字段间的关系),示例:

// 查询"价格高于成本2倍"的商品
db.products.find({ $where: "this.price > this.cost * 2" })

无法有效使用索引的原因

  • 执行机制$where会对集合中的每个文档执行JavaScript代码,逐行判断条件是否成立。这种逐文档扫描的方式本质上是“全表扫描”,索引无法直接加速这个过程。
  • 索引失效场景:即使字段pricecost有索引,$where也无法利用它们,因为索引是基于字段值的有序结构,而$where的逻辑是动态计算的(依赖两个字段的运算结果)。
2. $expr:使用聚合表达式查询

$expr允许在查询中使用聚合管道的表达式(如$gt$add等),支持字段间的比较,示例:

// 与上面$where等效的$expr查询
db.products.find({ $expr: { $gt: ["$price", { $multiply: ["$cost", 2] }] } })

通常无法使用索引的原因

  • 表达式的动态性$expr支持复杂运算(如字段间的加减乘除、逻辑组合),这些运算结果是动态生成的,而索引是基于字段原始值的有序结构,无法直接匹配运算后的结果。
  • 例外情况:若$expr中仅使用单个字段的简单判断(如$expr: { $eq: ["$status", "active"] }),MongoDB可能会尝试使用该字段的索引。但只要涉及多个字段的运算函数处理(如$substr$year),索引就会失效。

二、实例对比:索引生效 vs 失效

假设有orders集合,包含字段amount(金额)、discount(折扣)、finalPrice(最终价格),且amountdiscount有单字段索引。

1. 索引生效的查询(无$where/$expr
// 查询金额大于1000的订单(使用amount索引)
db.orders.find({ amount: { $gt: 1000 } })
  • 执行计划显示IXSCAN(索引扫描),直接利用amount索引定位符合条件的文档。
2. 索引失效的查询(使用$where/$expr
// 用$where查询"最终价格 = 金额 - 折扣"的订单
db.orders.find({ $where: "this.finalPrice === this.amount - this.discount" })// 用$expr查询同样逻辑
db.orders.find({ $expr: { $eq: ["$finalPrice", { $subtract: ["$amount", "$discount"] }] } })
  • 执行计划显示COLLSCAN(全表扫描),即使amountdiscount有索引,也不会被使用。
  • 原因:MongoDB需要计算每个文档的amount - discount结果,再与finalPrice比较,这个过程无法通过索引加速。

三、如何避免性能问题?

  1. 优先使用字段直接查询:能用普通查询条件(如{ amount: { $gt: 1000 } })实现的逻辑,就避免使用$where$expr

  2. 预计算结果字段:若业务频繁需要字段间运算(如finalPrice = amount - discount),可在文档中新增一个finalPrice字段,存储预计算结果,并为该字段创建索引:

    // 新增预计算字段后,用普通查询(可使用索引)
    db.orders.find({ finalPrice: { $gt: 500 } }) // 假设为finalPrice创建了索引
    
  3. 限制$where/$expr的使用场景:仅在其他方法无法实现时使用,且需配合过滤条件缩小范围(如先通过status: "paid"过滤,再用$expr处理):

    // 先通过索引字段过滤,减少$expr处理的文档数量
    db.orders.find({status: "paid", // 假设status有索引,先过滤出部分文档$expr: { $gt: ["$finalPrice", { $multiply: ["$amount", 0.8] }] }
    })
    

总结

$where$expr的灵活性是以牺牲性能为代价的——它们的动态计算逻辑与索引基于“字段原始值有序存储”的设计理念冲突,因此通常无法利用索引。在实际开发中,应优先通过索引友好的查询方式实现业务逻辑,仅在必要时谨慎使用这两个操作符,并做好性能测试。

http://www.lryc.cn/news/613720.html

相关文章:

  • [Oracle] LEAST()函数
  • 经常问的14002
  • Kafka生产者事务机制原理
  • 前端单元测试最佳实践(一)
  • 前端开发(HTML,CSS,VUE,JS)从入门到精通!第八天(Vue框架及其安装)(完结篇) 重点 ! ! !
  • 基于Web的交互式坐标系变换矩阵计算工具
  • 【Linux】Linux增删改查命令大全(附频率评级)
  • vue3 map和filter功能 用法
  • Odoo 18 → Odoo 19 功能改动对比表
  • Vue3 基本语法
  • day21|学习前端vue3框架和ts语言
  • pdf文件转word免费使用几个工具
  • CSS BFC
  • webrtc弱网-EncodeUsageResource类源码分析及算法原理
  • Spring Security自动处理/login请求,后端控制层没有 @PostMapping(“/login“) 这样的 Controller 方法
  • 设计模式(二)——策略模式
  • 冠雅新品 | 以“无形之光”守护双眸,以“无声之智”浸润生活
  • 基于R语言,“上百种机器学习模型”学习教程 | Mime包
  • 【昇腾】Atlas 500 A2 智能小站制卡从M.2 SATA盘启动Ubuntu22.04系统,重新上电卡死没进系统问题处理_20250808
  • 主播生活模拟器2|主播人生模拟器2 (Streamer Life Simulator 2)免安装中文版
  • 31-数据仓库与Apache Hive-Insert插入数据
  • Pinterest视觉营销自动化:亚矩阵云手机实例与多分辨率适配技术
  • 远期(Forward)交易系统全球金融市场解决方案报告
  • 32-Hive SQL DML语法之查询数据
  • 《Hive、HBase、StarRocks、MySQL、OceanBase 全面对比:架构、优缺点与使用场景详解》
  • 安装部署K8S集群环境(实测有效版本)
  • K8s 常见故障案例分析
  • ArgoCD 与 GitOps:K8S 原生持续部署的实操指南
  • hive-日期拆分为多行
  • 二、k8s 1.29 之 网络