MongoDB Cheat sheet: Tổng hợp tất cả truy vấn trong MongoDB

Khi làm việc với cơ sở dữ liệu, khả năng truy vấn dữ liệu là kỹ năng cốt lõi mà bất kỳ lập trình viên nào cũng cần nắm vững. Với MongoDB – cơ sở dữ liệu NoSQL phổ biến nhất hiện nay – cú pháp truy vấn khá linh hoạt, mạnh mẽ và gần gũi nhờ được thiết kế dựa trên JSON. Bài viết này sẽ giúp bạn có một cái nhìn rõ ràng hơn về cách truy vấn trong MongoDB.

Đọc bài viết này để hiểu rõ hơn về:

  • Các loại truy vấn trong MongoDB
  • Một số cách tối ưu truy vấn

Tổng quan về MongoDB

MongoDB là một hệ quản trị cơ sở dữ liệu NoSQL dạng tài liệu (document-oriented database). Nó được thiết kế để xử lý lượng dữ liệu lớn một cách nhanh chóng và linh hoạt. 

Thay vì lưu trữ dữ liệu trong các bảng (tables) với hàng và cột cố định như cơ sở dữ liệu quan hệ (RDBMS), MongoDB sử dụng document (tài liệu) làm đơn vị lưu trữ chính. Mỗi document được biểu diễn dưới dạng BSON (Binary JSON) – một định dạng nhị phân mở rộng của JSON, hỗ trợ thêm các kiểu dữ liệu như Date, ObjectId, Binary data, có thể linh hoạt thay đổi về số lượng và kiểu dữ liệu của các trường (fields). 

Chính sự linh hoạt này giúp MongoDB đặc biệt phù hợp với các ứng dụng hiện đại khi dữ liệu thay đổi liên tục và khó “ép buộc” vào một schema cứng nhắc.

Đọc chi tiết: MongoDB là gì? Định nghĩa và Hiểu rõ A-Z về MongoDB

Các khái niệm cơ bản trong MongoDB

  • Database (Cơ sở dữ liệu): Tập hợp các collections (bộ sưu tập). Một instance MongoDB có thể chứa nhiều databases độc lập với nhau.
  • Collection: Có thể hình dung như “bảng” trong SQL, nhưng không bị ràng buộc bởi cấu trúc cứng. Mỗi collection có thể chứa nhiều document với cấu trúc khác nhau.
  • Document (Tài liệu): Đơn vị dữ liệu cơ bản trong MongoDB, lưu dưới dạng BSON (hiển thị dưới dạng JSON khi thao tác). Ví dụ:
{ "name": "Alice", "age": 25, "email": "alice@example.com" }
  • Field (Trường): Các cặp key-value trong document. Ví dụ: name: "Alice" chính là một field.
  • _id: Mỗi document đều có một trường _id duy nhất đóng vai trò là primary key, được MongoDB tự động tạo nếu không chỉ định.

Điểm nổi bật của MongoDB là gì?

  • Linh hoạt về schema: Không cần định nghĩa trước, có thể thêm hoặc bớt trường khi dữ liệu thay đổi mà không cần chuyển đổi (migration) phức tạp như RDBMS.
  • Mở rộng dễ dàng: Nhờ cơ chế sharding (phân mảnh ngang), dữ liệu có thể chia nhỏ và phân phối trên nhiều máy chủ, đảm bảo hệ thống vận hành tốt ngay cả khi lượng dữ liệu khổng lồ.
    Tính sẵn sàng cao: Với replication (cơ chế Replica Set), dữ liệu được sao chép sang nhiều máy chủ, giúp giảm thiểu rủi ro mất mát khi có sự cố.
    Truy vấn phong phú: Hỗ trợ từ các câu lệnh đơn giản đến nâng cao như text search, truy vấn địa lý (geospatial queries) hay pipeline tổng hợp (aggregation pipeline) để xử lý dữ liệu phức tạp.
  • Thân thiện với hệ sinh thái hiện đại: Được sử dụng rộng rãi trong phát triển web, ứng dụng di động (mobile), big data, phân tích dữ liệu thời gian thực (real-time analytics) và cả IoT (Internet of Things). 

Các loại truy vấn trong MongoDB

Trước khi tìm hiểu các loại truy vấn, ta cần chuẩn bị sẵn một collection mẫu để dễ thực hành. Giả sử chúng ta đang có một collection users trong database test, dữ liệu ban đầu như sau:

db.test.insertMany([
  { name: "Alice", age: 25, email: "alice@gmail.com", gender: "Female", interests: ["Reading", "Traveling"] },
  { name: "Bob", age: 30, email: "bob@yahoo.com", gender: "Male", interests: ["Gaming", "Cooking"] },
  { name: "Charlie", age: 28, email: "charlie@gmail.com", gender: "Male", interests: ["Sports", "Traveling"] },
  { name: "David", age: 35, email: "david@hotmail.com", gender: "Male", interests: ["Cooking", "Reading"] },
  { name: "Eva", age: 22, email: "eva@gmail.com", gender: "Female", interests: ["Dancing", "Music"] }
])

Truy vấn đơn giản

  • Để lấy toàn bộ dữ liệu trong collection ta dùng cú pháp:
db.users.find()
  • Để lấy bản ghi đầu tiên ta dùng cú pháp:
db.users.findOne()

Kết quả: Trả về user đầu tiên MongoDB tìm thấy là Alice

Truy vấn với điều kiện

  • Tìm bản ghi với điều kiện cụ thể: Ví dụ để tìm user có tên là Alice:
db.users.find({ name: "Alice" })
  • Tìm user vừa có tên Alice vừa có tuổi 25:
db.users.find({ name: "Alice", age: 25 })

Kết quả: MongoDB chỉ trả về document của Alice nếu đồng thời thỏa cả 2 điều kiện (đây là toán tử AND ngầm định).

  • Truy vấn với nhiều điều kiện (AND)

Ví dụ để tìm user là nam và có tuổi > 25 ta dùng:

db.users.find({ $and: [ { age: { $gt: 25 } }, { gender: "Male" } ] })

Kết quả sẽ trả về Bob (30), Charlie (28), David (35).

Lưu ý: Trong hầu hết trường hợp, bạn không cần dùng $and một cách tường minh, vì MongoDB mặc định áp dụng AND khi có nhiều điều kiện. Cú pháp đơn giản hơn:

db.users.find({ age: { $gt: 25 }, gender: "Male" })
  • Truy vấn với toán tử OR

Ví dụ để tìm user có tuổi < 25 hoặc là nữ ta có cú pháp như sau:

db.users.find({ $or: [ { age: { $lt: 25 } }, { gender: "Female" } ] })

Kết quả sẽ trả về Alice (25, Female) và Eva (22, Female).

Truy vấn với toán tử

Toán tửMô tảVí dụ
$gtLớn hơndb.users.find({ age: { $gt: 25 } })
$gteLớn hơn hoặc bằngdb.users.find({ age: { $gte: 30 } })
$ltNhỏ hơndb.users.find({ age: { $lt: 30 } })
$lteNhỏ hơn hoặc bằngdb.users.find({ age: { $lte: 25 } })
$neKhác vớidb.users.find({ gender: { $ne: "Male" } })
$inTrong danh sáchdb.users.find({ name: { $in: ["Alice", "Eva"] } })
$ninKhông nằm trong danh sáchdb.users.find({ name: { $nin: ["Alice", "Eva"] } })
$regexKhớp chuỗidb.users.find({ email: { $regex: /gmail/ } })
  • Ví dụ để tìm user có tuổi lớn hơn 25:
db.users.find({ age: { $gt: 25 } })

Kết quả: Với câu lệnh trên sẽ trả về Bob (30), Charlie (28), David (35).

  • Ví dụ để tìm user có email chứa “gmail”:
db.users.find({ email: { $regex: /gmail/ } })

Kết quả sẽ trả về Alice, Charlie, Eva.

  • Ví dụ để tìm user có tên trong danh sách [“Alice”, “Eva”]:
db.users.find({ name: { $in: ["Alice", "Eva"] } })

Kết quả sẽ trả về Alice và Eva.

Một số câu truy vấn trong MongoDB khác

Projection / Field Filtering (chỉ lấy trường cần thiết)

Đôi khi dữ liệu có rất nhiều field, nhưng bạn chỉ muốn xem một vài field quan trọng. Ví dụ để chỉ lấy trường name và email, bỏ qua các field khác thì ta dùng cú pháp:

db.users.find({}, { name: 1, email: 1, _id: 0 })

Kết quả trả về chỉ hiển thị nameemail mà không có _id. Trong đó:

  • 1: nghĩa là bao gồm (include)
  • 0: nghĩa là loại trừ (exclude)

Lưu ý: Bạn không thể kết hợp inclusion và exclusion trong cùng một truy vấn, ngoại trừ trường hợp đặc biệt với _id.

Sorting (Sắp xếp dữ liệu)

Dùng để sắp xếp kết quả theo một hoặc nhiều trường. Ví dụ để sắp xếp danh sách user theo tuổi giảm dần, ta dùng cú pháp:

    db.users.find().sort({ age: -1 })

    Kết quả trả về danh sách user từ người lớn tuổi nhất đến trẻ nhất. Trong đó:

    • -1: là giảm dần
    • 1: là tăng dần

    Limit (Giới hạn số kết quả)

    Ví dụ để chỉ lấy 2 user đầu tiên trong tập kết quả, ta dùng cú pháp:

      db.users.find().limit(2)

      Kết quả trả về chỉ gồm 2 document đầu tiên của collection.

      Skip (Bỏ qua số bản ghi)

      Thường kết hợp với limit để phân trang. Ví dụ bỏ qua 2 bản ghi đầu và lấy 2 bản ghi tiếp theo:

      db.users.countDocuments({ gender: "Female" })

      Hữu ích cho phân trang (pagination). Ví dụ: trang 1 sẽ lấy các bản ghi bằng skip(0).limit(10), còn trang 2 là skip(10).limit(10) – nghĩa là bỏ qua 10 bản ghi đầu tiên và lấy tiếp 10 bản ghi kế tiếp

      Count Documents (Đếm số bản ghi thỏa điều kiện)

      Ví dụ để đếm số lượng user là nữ, ta dùng cú pháp:

      db.users.countDocuments({ gender: "Female" })

      Kết quả trả về một con số, ví dụ như 5.

      Distinct (Lấy giá trị duy nhất của một field)

      Thay vì lấy dữ liệu, chỉ muốn biết có bao nhiêu bản ghi thỏa điều kiện. Ví dụ:

      db.users.distinct("gender")

      Kết quả sẽ trả về ["Male", "Female"].

      Tổng hợp các lệnh truy vấn trong MongoDB

      LệnhChức năngCách sử dụngVí dụ ứng dụng
      db.test.find()Lấy toàn bộ document trong collectionKhông cần tham số, trả về tất cả dữ liệuXem nhanh toàn bộ dữ liệu trong collection để kiểm tra nhập liệu
      db.test.findOne({field: value})Lấy 1 document đầu tiên thỏa điều kiệnTruyền vào object điều kiệnTìm thông tin một người dùng cụ thể theo tên
      db.test.find({field1: value1, field2: value2})Truy vấn nhiều trường cùng lúcGhi các cặp key-value trong objectTìm user vừa có name = Enayet và age = 36
      db.test.find({field: {$exists: true}})Kiểm tra sự tồn tại của trườngDùng $exists: true/falseLấy tất cả tài liệu có chứa trường age (hoặc không có trường này)
      db.test.find({field: {$eq: value}})So sánh bằngDùng $eq trong object điều kiệnTìm user có age = 36
      db.test.find({field: {$ne: value}})So sánh khácDùng $ne trong object điều kiệnTìm tất cả user không phải Male
      db.test.find({field: {$gt: value}})Lớn hơnDùng $gt trong object điều kiệnTìm người lớn tuổi hơn 20
      db.test.find({field: {$gte: value}})Lớn hơn hoặc bằngDùng $gte trong object điều kiệnLấy người 20 tuổi trở lên
      db.test.find({field: {$lt: value}})Nhỏ hơnDùng $lt trong object điều kiệnLấy trẻ em dưới 5 tuổi
      db.test.find({field: {$lte: value}})Nhỏ hơn hoặc bằngDùng $lte trong object điều kiệnLấy trẻ <= 3 tuổi
      db.test.find({field: {$in: [v1, v2]}})Thuộc tập giá trịDùng $in với array giá trịTìm user tên là Enayet hoặc Mariyam
      db.test.find({field: {$nin: [v1, v2]}})Không thuộc tập giá trịDùng $nin với array giá trịLấy người có tuổi khác 1 và 3
      db.test.find({ $and: [cond1, cond2] })Kết hợp nhiều điều kiện (AND tường minh)Dùng $and với mảng các objectTìm user age > 20gender = Male
      db.test.find({ $or: [cond1, cond2] })Thỏa ít nhất 1 điều kiện (OR)Dùng $or với mảng các objectTìm người dưới 5 tuổi hoặc giới tính Female
      db.test.find({arrayField: {$all: [v1, v2]}})Array chứa tất cả giá trịDùng $all với arrayTìm người có cả sở thích Travelling & Cooking
      db.test.find({arrayField: {$elemMatch: {...}}})Array object thỏa tất cả điều kiệnDùng $elemMatchTìm người có skill JavaScript ở mức Intermediate

      Làm thế nào để tối ưu truy vấn trong MongoDB?

      1. Sử dụng Index (Chỉ mục)

      Index giúp MongoDB tìm dữ liệu nhanh hơn, giống như mục lục trong sách. Thay vì phải lật từng trang, MongoDB chỉ cần tra mục lục để đến đúng vị trí. Index đặc biệt quan trọng khi collection có hàng triệu documents.

      Ví dụ để tìm user theo email nhanh hơn, ta tạo index cho field email như sau:

      // Tạo index trên field "email"
      db.users.createIndex({ email: 1 })
      
      // Truy vấn nhanh hơn khi có index

      Kết quả: Với index trên, MongoDB sẽ truy cập thẳng đến document có email = alice@example.com thay vì duyệt toàn bộ dữ liệu.

      • Các loại index phổ biến:
      // Single Field Index
      db.users.createIndex({ age: 1 })
      
      // Compound Index (index nhiều trường) - thứ tự quan trọng!
      db.users.createIndex({ gender: 1, age: -1 })
      
      // Unique Index (đảm bảo giá trị duy nhất)
      db.users.createIndex({ email: 1 }, { unique: true })
      
      // Text Index (cho full-text search)
      db.users.createIndex({ name: "text", interests: "text" })
      
      // Multikey Index (tự động cho array fields)
      db.users.createIndex({ interests: 1 })

      Lưu ý:

      • Index tăng tốc độ đọc nhưng làm chậm các thao tác ghi như insert, update, delete vì dữ liệu phải được cập nhật thêm trong cấu trúc chỉ mục.
      • Mỗi index chiếm thêm dung lượng lưu trữ, do đó việc tạo quá nhiều index không cần thiết có thể làm giảm hiệu suất tổng thể.
      • Cách kiểm tra index hiện có:
      db.users.getIndexes()
      • Cách xóa index không còn sử dụng:
      db.users.dropIndex("index_name")

      2. Projection – Chỉ lấy dữ liệu cần thiết

      Khi truy vấn, MongoDB mặc định trả về toàn bộ các field trong document. Nhưng nếu chỉ cần vài trường thông tin, việc lấy tất cả sẽ lãng phí tài nguyên, làm truy vấn chậm hơn và tốn băng thông. Điều này càng đáng chú ý khi document chứa trường lớn như danh sách (array) hoặc tài liệu lồng nhau (embedded document).

      Vì vậy chỉ lấy những dữ liệu cần thiết để tránh tốn tài nguyên. Ví dụ để chỉ lấy name và email (bỏ qua _id), ta dùng cú pháp:

      db.users.find({}, { name: 1, email: 1, _id: 0 })

      Projection nâng cao

      // Loại trừ một trường cụ thể (exclusion)
      db.users.find({}, { interests: 0 })
      
      // Lấy phần tử đầu tiên của array
      db.users.find({}, { interests: { $slice: 1 } })
      
      // Lấy 2 phần tử cuối của array
      db.users.find({}, { interests: { $slice: -2 } })

      3. Sử dụng Explain để phân tích truy vấn

      MongoDB hỗ trợ lệnh explain() để phân tích truy vấn đang chạy nhanh hay chậm, có dùng index hay không. Ví dụ để kiểm tra truy vấn tìm user có age > 25, ta dùng cú pháp:

      db.users.find({ age: { $gt: 25 } }).explain("executionStats")

      Kết quả trả về cho biết MongoDB đã quét bao nhiêu document, có dùng index hay không rồi từ đó tinh chỉnh thêm.

      4. Giới hạn (Limit) và phân trang

      Nếu bạn chỉ cần một phần dữ liệu (ví dụ: top 10 bản ghi mới nhất), hãy dùng limit() hoặc kết hợp với skip() để phân trang. Tuy nhiên skip() với số lớn rất chậm, nên dùng phương pháp phân trang dựa trên range queries. Ví dụ:

      // Lấy 10 user đầu tiên, sắp xếp theo tuổi giảm dần
      db.users.find().sort({ age: -1 }).limit(10)
      
      // Lấy trang thứ 2 (bỏ qua 10 bản ghi đầu)
      db.users.find().sort({ age: -1 }).skip(10).limit(10)

      Phân trang tối ưu hơn (Range-based pagination):

      // Trang đầu tiên
      db.users.find().sort({ _id: -1 }).limit(10)
      
      // Trang tiếp theo (dùng _id của bản ghi cuối trang trước)
      db.users.find({ _id: { $lt: lastSeenId } }).sort({ _id: -1 }).limit(10)

      5. Sử dụng Covered Query

      Covered Query là loại truy vấn mà toàn bộ các trường được truy vấn và trả về đều nằm trong index. Nhờ đó, MongoDB không cần đọc document gốc trong collection, giúp tăng tốc độ truy vấn.

      Ví dụ:

      // Tạo compound index
      db.users.createIndex({ gender: 1, age: 1, name: 1 })
      
      // Covered query - cực nhanh!
      db.users.find(
        { gender: "Male", age: { $gt: 25 } },
        { gender: 1, age: 1, name: 1, _id: 0 }
      )
      • Trong đó: Cả điều kiện lọc (gender, age) và các trường trả về (gender, age, name) đều nằm trong index { gender: 1, age: 1, name: 1 }. Vì vậy, MongoDB có thể trả kết quả trực tiếp từ index mà không cần truy cập document gốc.

      Khi kiểm tra bằng lệnh explain(), ta sẽ thấy:

      "totalDocsExamined" : 0

      Nghĩa là không có document nào được đọc từ đĩa, tất cả dữ liệu đều được lấy từ index

      Câu hỏi thường gặp về các truy vấn trong MongoDB

      Truy vấn trong MongoDB có khác gì so với SQL không?

      Có. MongoDB dùng cú pháp dựa trên JSON thay vì SQL.

      Ví dụ, thay vì SELECT * FROM users WHERE age > 25, ta sẽ dùng:

      db.users.find({ age: { $gt: 25 } })

      MongoDB có hỗ trợ truy vấn phức tạp không?

      Có. MongoDB cung cấp nhiều toán tử (operators) như $and, $or, $in, $regex và hệ thống Aggregation Pipeline để xử lý dữ liệu phức tạp như lọc, nhóm, tính toán.

      Truy vấn trong MongoDB có phân biệt hoa thường không?

      Có. Mặc định MongoDB phân biệt chữ hoa và thường. Tuy nhiên, ta có thể dùng $regex kèm flag i để bỏ qua phân biệt.

      db.users.find({ name: { $regex: /^alice$/i } })

      Tổng kết

      Truy vấn là bước nền tảng để bạn khai thác tối đa sức mạnh của MongoDB. Chỉ cần nắm vững cú pháp cơ bản, hiểu rõ các toán tử và biết cách tối ưu như ITviec đã chia sẻ ở trên, bạn đã có thể tự tin xây dựng những hệ thống xử lý dữ liệu linh hoạt, nhanh chóng và hiệu quả, từ đó dễ dàng tiến tới các kỹ năng nâng cao như Aggregation Pipeline, Indexing hay Data Modeling.

      TÁC GIẢ
      Mỹ Duyên
      Mỹ Duyên

      Content Writer

      Là cử nhân ngành Data Science, Duyên có hơn 1 năm kinh nghiệm nghiên cứu trong ngành Data và tập trung vào AI, phân tích dữ liệu. Thông qua những bài viết từ cơ bản đến nâng cao thuộc lĩnh vực cơ sở dữ liệu, Duyên mang đến cho độc giả những cái nhìn toàn diện và mới mẻ về thế giới công nghệ thông tin và dữ liệu.