查询语法入门
MongoDB 不使用 SQL 作为主要查询接口。mongosh 和驱动通过命令及 BSON 文档表达查询,字段名是数据,$gt、$set、$group 等 Operator 表达操作。下面以 MySQL 习惯对照 MongoDB Query API。
示例文档
javascript
use notebook
db.users.insertOne({
username: "alice",
enabled: true,
balance: Decimal128("100.00"),
tags: ["user", "vip"],
profile: { city: "Hefei", level: 3 },
createdAt: new Date()
})use 只切换当前 Database,Database 和 Collection 通常在首次写入时创建。生产环境可先显式创建 Collection、Validator 和 Index,避免错误名称或错误类型因首次写入被固化。
CRUD 对照
| 目标 | MySQL | MongoDB |
|---|---|---|
| 插入一行 | INSERT INTO | insertOne(document) |
| 批量插入 | 多组 VALUES | insertMany(documents) |
| 查询 | SELECT ... WHERE | find(filter, projection) |
| 更新 | UPDATE ... SET | updateOne/updateMany(filter, update) |
| 删除 | DELETE ... WHERE | deleteOne/deleteMany(filter) |
| 主键 | 自定义 Primary Key | _id,缺省生成 ObjectId |
javascript
db.users.find(
{ enabled: true, balance: { $gte: Decimal128("50.00") } },
{ username: 1, balance: 1, _id: 0 }
).sort({ balance: -1 }).limit(20)相当于:
sql
SELECT username, balance
FROM users
WHERE enabled = TRUE AND balance >= 50.00
ORDER BY balance DESC
LIMIT 20;条件操作符
| SQL | MongoDB Filter |
|---|---|
age = 18 | { age: 18 } |
age <> 18 | { age: { $ne: 18 } } |
age >= 18 | { age: { $gte: 18 } } |
status IN (...) | { status: { $in: [...] } } |
a = 1 AND b = 2 | { a: 1, b: 2 } |
a = 1 OR b = 2 | { $or: [{ a: 1 }, { b: 2 }] } |
field IS NULL | { field: null },同时匹配 null 和缺失 |
field IS NOT NULL | 需结合 $ne 与 $exists 明确缺失语义 |
LIKE 'abc%' | { name: /^abc/ },索引能力取决于正则形式与 Collation |
MongoDB 区分 Field 缺失与 BSON Null,但 { field: null } 会同时匹配两者。需要精确语义时使用 $exists、$type 等条件组合。
嵌套对象和数组
javascript
db.users.find({ "profile.city": "Hefei" })
db.users.find({ tags: "vip" })
db.users.find({ tags: { $all: ["user", "vip"] } })对象数组中要求同一个元素同时满足多个条件时使用 $elemMatch:
javascript
db.orders.find({
items: {
$elemMatch: {
productId: 1001,
quantity: { $gte: 2 }
}
}
})更新操作
javascript
db.users.updateOne(
{ username: "alice" },
{
$set: { enabled: true, "profile.city": "Shanghai" },
$inc: { loginCount: 1 },
$addToSet: { tags: "active" },
$currentDate: { updatedAt: true }
}
)updateOne 默认不会插入新文档,加入 { upsert: true } 才表示没有匹配时创建。与 MySQL ON DUPLICATE KEY UPDATE 类似,可靠 Upsert 仍需要 Unique Index 防止并发下产生重复业务键。
不要把普通对象直接作为 Update 参数来表达 $set;不同 API 中它可能表示替换整个文档或直接报错,应明确使用更新操作符或 replaceOne。
聚合与关联
javascript
db.orders.aggregate([
{ $match: { status: "paid" } },
{ $group: { _id: "$userId", total: { $sum: "$amount" } } },
{ $sort: { total: -1 } },
{ $limit: 20 }
])SQL 对照:
sql
SELECT user_id, SUM(amount) AS total
FROM orders
WHERE status = 'paid'
GROUP BY user_id
ORDER BY total DESC
LIMIT 20;关联使用 $lookup:
javascript
db.orders.aggregate([
{
$lookup: {
from: "users",
localField: "userId",
foreignField: "_id",
as: "user"
}
},
{ $unwind: "$user" }
])如果核心请求持续需要多层 $lookup,应重新检查数据是否应嵌入或冗余,而不是只优化关联语法。
分页
javascript
db.users.find({ enabled: true })
.sort({ createdAt: -1, _id: -1 })
.skip(40)
.limit(20)skip 与 SQL OFFSET 类似,深分页会扫描并丢弃前序结果。Keyset Pagination 使用上一页最后一个排序键继续查询:
javascript
db.users.find({
enabled: true,
$or: [
{ createdAt: { $lt: lastCreatedAt } },
{ createdAt: lastCreatedAt, _id: { $lt: lastId } }
]
}).sort({ createdAt: -1, _id: -1 }).limit(20)需要建立与 Filter 和 Sort 匹配的复合索引。
MySQL 使用习惯迁移
| MySQL 习惯 | MongoDB 调整 |
|---|---|
| 先按实体拆表,再通过 JOIN 组合 | 先确定聚合边界,再选择嵌入或引用 |
| Database/Table/Row | Database/Collection/Document |
| 自增整数主键 | 默认 ObjectId,也可使用业务 _id,不建议集中式自增热点 |
| 列类型由 DDL 强制 | BSON 类型随值保存,关键结构用 Schema Validation 约束 |
NULL 与列存在 | 区分 null 和 Field Missing |
UPDATE SET count = count + 1 | 使用 $inc 原子更新 |
GROUP BY | 使用 Aggregation $group |
JOIN | 嵌入、引用查询或 $lookup |
EXPLAIN | 使用 explain("executionStats") |
| 事务是默认建模工具 | 优先单文档原子性,必要时使用多文档事务 |