MongoDB find(): Chiếc chìa khóa “vạn năng” trong MongoDB

Nếu coi MongoDB là một “kho tàng dữ liệu”, thì MongoDB find() chính là chiếc chìa khóa giúp bạn mở ra đúng ngăn tủ mình cần. Từ việc lấy toàn bộ dữ liệu, lọc theo điều kiện, cho đến áp dụng các toán tử nâng cao, tất cả đều có thể bắt đầu chỉ với một lệnh find().

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

  • Các loại truy vấn với find() trong MongoDB
  • Tối ưu hóa truy vấn với index
  • Một số lỗi thường gặp với find() trong MongoDB

Tổng quan về find() trong MongoDB

Tổng quan về MongoDB

MongoDB là một cơ sở dữ liệu NoSQL dạng document, được xây dựng để lưu trữ dữ liệu linh hoạt dưới định dạng BSON (Binary JSON). Điểm mạnh của MongoDB nằm ở khả năng không bắt buộc schema cố định, nhờ đó mà người dùng có thể thêm trường mới hoặc thay đổi cấu trúc của dữ liệu mà không cần chỉnh sửa toàn bộ hệ thống. Chính vì vậy, MongoDB đặc biệt phù hợp cho những ứng dụng web hoặc hệ thống hiện đại, nơi mà dữ liệu thay đổi nhanh chóng.

Trong MongoDB, dữ liệu được tổ chức theo ba tầng:

  • Database: Đây là “kho chứa” chính, nơi bạn quản lý nhiều tập dữ liệu khác nhau. Một ứng dụng thường sẽ có ít nhất một database.
  • Collection: Bên trong database, dữ liệu được chia thành nhiều collection. Collection giống như “bảng” trong SQL nhưng không bắt buộc phải có schema cố định.
  • Document: Đây là đơn vị dữ liệu nhỏ nhất, lưu dưới dạng BSON trong storage nhưng được biểu diễn dưới dạng JSON khi làm việc. Mỗi document có một trường _id unique được MongoDB tự động tạo nếu không được chỉ định. Ví dụ một document rất đơn giản có thể là:
{ 
  "_id": ObjectId("507f1f77bcf86cd799439011"),
  "name": "Alice", 
  "age": 25, 
  "email": "alice@example.com" 
}

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

find() trong MongoDB

Khi đã có dữ liệu được lưu trong collection, công cụ thường dùng nhất để lấy dữ liệu ra chính là phương thức find(). Đây là “chiếc chìa khóa” giúp người dùng truy vấn, tìm kiếm và lọc thông tin trong MongoDB. Ví dụ để lấy tất cả dữ liệu từ collection test, ta chỉ cần:

db.test.find()

Hoặc trong MongoDB Compass/mongosh hiện đại:

db.users.find({})

Câu lệnh trên sẽ trả về toàn bộ document trong collection users. Mặc định, find() sẽ trả về một cursor chứ không phải toàn bộ kết quả ngay lập tức, giúp tối ưu hiệu năng khi làm việc với dữ liệu lớn.

Đây là bước khởi đầu cơ bản nhất để làm quen với việc truy vấn trong MongoDB, và trong những phần tiếp theo chúng ta sẽ cùng đi sâu hơn vào nhiều cách sử dụng find() linh hoạt hơn.

Các loại truy vấn với find()

Cú pháp cơ bản

Cú pháp chung của find() trong MongoDB là:

db.<collection>.find(query, projection)
  • Trong đó:
    • query: điều kiện để lọc dữ liệu (có thể bỏ trống để lấy tất cả).
    • projection: xác định những field nào được hiển thị.

Ví dụ:

db.users.find()
// Hoặc tương đương:
db.users.find({})

Kết quả: Truy vấn này sẽ trả về tất cả các document trong collection users

  • Ví dụ với projection:
// Chỉ lấy name và age, không lấy _id
db.users.find({}, { name: 1, age: 1, _id: 0 })

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

Toán tửÝ nghĩaỨng dụng
$eqBằngLấy tất cả các document có field bằng đúng giá trị chỉ định.
$neKhácLấy tất cả các document có field khác giá trị chỉ định.
$gtLớn hơnLấy các document có field lớn hơn giá trị chỉ định.
$gteLớn hơn hoặc bằngLấy các document có field lớn hơn hoặc bằng giá trị chỉ định.
$ltNhỏ hơnLấy các document có field nhỏ hơn giá trị chỉ định.
$lteNhỏ hơn hoặc bằngLấy các document có field nhỏ hơn hoặc bằng giá trị chỉ định.
$inNằm trong danh sáchKiểm tra giá trị có nằm trong mảng
$ninKhông nằm trong danh sáchKiểm tra giá trị không nằm trong mảng

Ví dụ 1: Lấy tất cả user có tuổi bằng 25, ta dùng toán tử $eq với cú pháp:

db.users.find({ age: { $eq: 25 } })
// Hoặc viết gọn hơn (implicit $eq):
db.users.find({ age: 25 })

Ví dụ 2: Tìm user có tuổi khác 25, ta dùng toán tử $ne với cú pháp:

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

Ví dụ 3:  Tìm user có tuổi lớn hơn 25, ta dùng toán tử $gt với cú pháp:

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

Ví dụ 4: Kết hợp nhiều toán tử so sánh:

// Tìm user có tuổi từ 18 đến 30
db.users.find({ age: { $gte: 18, $lte: 30 } })

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

Toán tửÝ nghĩaỨng dụng
$andVà (AND)Trả về document khi tất cả điều kiện đều đúng.
$orHoặc (OR)Trả về document khi ít nhất một điều kiện đúng.
$notPhủ định (NOT)Đảo ngược điều kiện
$norKhông hoặc (NOR)Trả về document khi TẤT CẢ điều kiện đều sai

Ví dụ:

  1. Tìm user có tuổi lớn hơn 20 VÀ giới tính là nam (Male), ta dùng toán tử $and với cú pháp:
db.users.find({
  $and: [
    { age: { $gt: 20 } },
    { gender: "Male" }
  ]
})

// Hoặc viết gọn hơn (implicit $and):
db.users.find({
  age: { $gt: 20 },
  gender: "Male"
})
  1. Tìm user có sở thích là “Travelling” hoặc “Gaming”, ta dùng toán tử $or với cú pháp:
db.users.find({
  $or: [
    { interests: "Travelling" },
    { interests: "Gaming" }
  ]
})

// Nếu cùng một field, có thể dùng $in:
db.users.find({ 
  interests: { $in: ["Travelling", "Gaming"] } 
})
  1. Tìm user có tuổi là 18 hoặc 25, ta dùng toán tử $in với cú pháp:
db.users.find({ age: { $in: [18, 25] } })
  1. Tìm user có tên không phải là “Alice” hoặc “Bob”, ta dùng toán tử $nin với cú pháp:
db.users.find({ name: { $nin: ["Alice", "Bob"] } })
  1. Tìm user không phải trẻ em (nghĩa là tuổi không nhỏ hơn 18) ta dùng $not như sau:
db.users.find({ age: { $not: { $lt: 18 } } })
  1. Tìm user không phải là nam và không sống ở Hà Nội ta dùng $nor như sau:
db.users.find({
  $nor: [
    { gender: "Male" },
    { city: "Hanoi" }
  ]
})

Truy vấn trong mảng

Giả sử ta có collection users chứa các document sau:

{
  name: "Alice",
  interests: ["Travelling", "Cooking", "Gaming"],
  skills: [
    { name: "JavaScript", level: "Intermediate" },
    { name: "Python", level: "Beginner" }
  ],
  friends: ["Bob", "Charlie"]
}
  1. Để tìm user có sở thích vừa “Travelling” vừa “Gaming”, ta dùng toán tử $all với cú pháp:
db.users.find({ interests: { $all: ["Travelling", "Gaming"] } })

Kết quả sẽ trả về document có field interests chứa đủ cả hai giá trị “Travelling” và “Gaming”.

  1. Để tìm user có MỘT TRONG các sở thích, ta dùng $in:
db.users.find({ interests: { $in: ["Travelling", "Gaming"] } })
  1. Để tìm user có kỹ năng JavaScript với level = “Intermediate”, ta dùng toán tử $elemMatch:
db.users.find({
  skills: { $elemMatch: { name: "JavaScript", level: "Intermediate" } }
})

Kết quả sẽ trả về document có mảng skills trong đó có ít nhất một object thỏa điều kiện name = JavaScriptlevel = Intermediate.

  1. So sánh với query không dùng $elemMatch:
// Query SAI - tìm skills có name="JavaScript" HOẶC level="Intermediate"
db.users.find({
  "skills.name": "JavaScript",
  "skills.level": "Intermediate"
})
// Query này có thể match document có skill Python level Intermediate 
// và skill JavaScript level Advanced (không phải cùng một object)
  1. Tìm user có đúng 2 người bạn trong danh sách friends, ta dùng toán tử $size:
db.users.find({ friends: { $size: 2 } })

Kết quả sẽ trả về document mà mảng friends có đúng 2 phần tử.

  1. Các cách query mảng khác:
// Tìm theo vị trí cụ thể trong mảng (index 0)
db.users.find({ "interests.0": "Travelling" })

// Tìm mảng chứa chính xác các phần tử (thứ tự và số lượng phải khớp)
db.users.find({ interests: ["Travelling", "Cooking", "Gaming"] })

// Tìm document có mảng không rỗng
db.users.find({ interests: { $exists: true, $ne: [] } })

// Sử dụng $slice trong projection để lấy một phần mảng
db.users.find({}, { interests: { $slice: 2 } }) // Lấy 2 phần tử đầu
db.users.find({}, { interests: { $slice: -1 } }) // Lấy phần tử cuối

Truy vấn với Sort, Limit và Skip

Sort()

Dùng để sắp xếp các document theo thứ tự tăng dần (1) hoặc giảm dần (-1) dựa trên một hoặc nhiều trường.

Ví dụ để sắp xếp danh sách người dùng theo tuổi giảm dần, ta có cú pháp:

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

Trong đó: { age: -1 } nghĩa là sắp xếp theo trường age theo thứ tự giảm dần. Nếu muốn tăng dần thì dùng { age: 1 }.

Ví dụ sắp xếp theo nhiều trường:

// Sắp xếp theo age giảm dần, nếu age bằng nhau thì sắp xếp theo name tăng dần
db.users.find().sort({ age: -1, name: 1 })

Limit()

Dùng để giới hạn số lượng document được trả về, rất hữu ích khi bạn chỉ cần một phần dữ liệu nhỏ (ví dụ như lấy top 5 kết quả).

Ví dụ để lấy 3 người dùng đầu tiên trong danh sách, ta có cú pháp:

db.users.find().limit(3)
  • Trong đó: limit(3) giúp MongoDB chỉ trả về tối đa 3 document đầu tiên.

Skip()

Dùng để bỏ qua một số lượng document nhất định trong kết quả truy vấn. Nó thường được kết hợp với limit() để phân trang dữ liệu (pagination).

Ví dụ để bỏ qua 2 người dùng đầu tiên và lấy tiếp 3 người dùng sau đó, ta có cú pháp:

db.users.find().skip(2).limit(3)
  • Trong đó:
    • skip(2) bỏ qua 2 document đầu tiên.
    • limit(3) sau đó lấy 3 document tiếp theo.

Truy vấn nâng cao với text search và regex

Được MongoDB thiết kế để tìm kiếm toàn văn bản (full-text search).

Tính năng này đặc biệt hữu ích khi ta cần tìm kiếm dữ liệu dạng chuỗi trong một collection có nhiều văn bản, chẳng hạn mô tả sản phẩm, tiêu đề bài viết hoặc nội dung comment. Tuy nhiên để dùng $text, ta cần tạo text index trước.

Ví dụ để tìm tất cả sản phẩm có chứa từ khóa phone trong trường description, ta có cú pháp:

// Tạo text index trên trường "description"
db.products.createIndex({ description: "text" })

// Truy vấn với từ khóa "phone"
db.products.find({ $text: { $search: "phone" } })
  • Trong đó:
    • createIndex({ description: "text" }) tạo một chỉ mục text trên trường description.
    • $text: { $search: "phone" } giúp MongoDB trả về tất cả document có chứa từ “phone” trong description.

Regex (Regular Expression)

Là biểu thức chính quy cho phép tìm kiếm linh hoạt hơn, ví dụ tìm chuỗi bắt đầu bằng một ký tự cụ thể, chứa từ khóa ở giữa hoặc kết thúc bằng mẫu nào đó. Điểm mạnh của Regex là ta không cần tạo index trước, tuy nhiên với dữ liệu lớn thì hiệu năng có thể kém hơn $text.

Ví dụ để tìm sản phẩm có description bắt đầu bằng từ Smart, ta có cú pháp:

db.products.find({ description: { $regex: /^Smart/ } })

// Hoặc dùng string pattern
db.products.find({ description: { $regex: "^Smart" } })

Trong đó: /^Smart/ nghĩa là tìm tất cả chuỗi bắt đầu bằng từ “Smart”, ví dụ: “Smartphone”, “Smartwatch”.

Ví dụ để tìm sản phẩm có description chứa từ Pro ở bất kỳ vị trí nào, ta có cú pháp:

db.products.find({ description: { $regex: /Pro/ } })

Trong đó: /Pro/ nghĩa là chuỗi nào có chứa từ “Pro” thì sẽ được chọn, ví dụ “iPhone Pro”, “MacBook Pro”.

  • Các options của Regex:
// Case-insensitive search (không phân biệt hoa thường)
db.products.find({ 
  description: { 
    $regex: /pro/i 
  } 
})
// Hoặc:
db.products.find({ 
  description: { 
    $regex: "pro", 
    $options: "i" 
  } 
})

// Multiline mode (^ và $ match đầu/cuối mỗi dòng)
db.products.find({ 
  description: { 
    $regex: /^Smart/m 
  } 
})

// Kết thúc bằng pattern
db.products.find({ 
  description: { 
    $regex: /Pro$/ 
  } 
})

// Wildcard patterns
db.products.find({ 
  description: { 
    $regex: /Smart.*Pro/ 
  } 
})

Cách tối ưu hóa truy vấn với Index

Trong MongoDB, Index (chỉ mục) là công cụ quan trọng để tăng tốc độ truy vấn. Khi không có index, MongoDB phải quét toàn bộ collection (full collection scan) để tìm dữ liệu nên sẽ ảnh hưởng đến tốc độ khi dữ liệu lớn.

Với index, MongoDB có thể nhảy thẳng đến vị trí cần thiết, tương tự như việc tra cứu một mục trong mục lục của cuốn sách thay vì phải đọc từ đầu đến cuối.

Tạo Index cơ bản

Để tối ưu hóa các truy vấn thường xuyên dùng, ta có thể tạo index trên một hoặc nhiều trường dữ liệu. Ví dụ để tạo index trên trường age giúp tăng tốc các truy vấn theo tuổi, ta có cú pháp:

db.users.createIndex({ age: 1 })
  • Trong đó:
    • { age: 1 } nghĩa là index được tạo theo thứ tự tăng dần. Nếu muốn giảm dần thì dùng { age: -1 }.
    • Sau khi tạo index này, các truy vấn như db.users.find({ age: 25 }) sẽ chạy nhanh hơn đáng kể.

Index trên nhiều trường (Compound Index)

Ví dụ để tăng tốc các truy vấn lọc theo agegender, ta có cú pháp:

db.users.createIndex({ age: 1, gender: 1 })

Text Index (Tìm kiếm toàn văn bản)

MongoDB hỗ trợ text index để tìm kiếm từ khóa trong các trường dạng chuỗi. Ví dụ: để tìm người dùng có chứa từ khóa "developer" trong mô tả (description), ta tạo index như sau:

db.users.createIndex({ description: "text" })
db.users.find({ $text: { $search: "developer" } })
  • Trong đó:
    • createIndex({ description: "text" }) cho phép tìm kiếm toàn văn trong trường description.
    • $search: "developer" sẽ trả về tất cả document có chứa từ khóa “developer”.

Xem và Xóa Index

  • Xem danh sách index hiện có:
db.users.getIndexes()
  • Xóa một index:
db.users.dropIndex("age_1")
  • Trong đó: "age_1" là tên tự động mà MongoDB đặt cho index trên trường age với thứ tự tăng dần.

Các lỗi thường gặp với find()

1. Nhầm lẫn giữa điều kiện và field projection

Một số người nhầm rằng field projection cũng viết chung trong object điều kiện, dẫn đến kết quả không đúng. Ví dụ lỗi như sau:

db.users.find({ name: "Alice", age: 25, email: 1 })  //  Sai

Trong đó email: 1 đáng lẽ phải nằm trong object projection. Cách đúng như sau:

// ĐÚNG - tách rõ query và projection
db.users.find(
  { name: "Alice", age: 25 },  // Query conditions
  { email: 1 }                   // Projection
)

// Các ví dụ projection khác:
db.users.find(
  { age: { $gt: 18 } },
  { name: 1, email: 1, _id: 0 }  // Chỉ lấy name, email, bỏ _id
)

db.users.find(
  {},
  { password: 0 }  // Lấy tất cả fields TRỪ password
)

2. Không kiểm soát số lượng kết quả trả về

Mặc định find() sẽ trả về toàn bộ documents phù hợp, dễ gây nghẽn khi dữ liệu lớn. Người mới thường quên giới hạn số lượng bằng limit().

Ví dụ:

// NGUY HIỂM với collection lớn
db.users.find()

// AN TOÀN - giới hạn số lượng
db.users.find().limit(10)  // Chỉ lấy 10 documents đầu tiên

// Kết hợp với sort để lấy top N
db.users.find().sort({ createdAt: -1 }).limit(5)  // 5 users mới nhất

3. Không tạo index khi dữ liệu lớn

find() vẫn chạy, nhưng nếu collection có hàng triệu documents và không có index, thì sẽ rất chậm. Đây không phải lỗi cú pháp, mà là lỗi về hiệu năng. 

// Query chậm với data lớn
db.users.find({ email: "user@example.com" })  // COLLSCAN

// Tạo index trước
db.users.createIndex({ email: 1 })
db.users.find({ email: "user@example.com" })  // IXSCAN - nhanh hơn

Câu hỏi thường gặp về MongoDB find()

find()findOne() khác nhau như thế nào?

find() được dùng để truy vấn nhiều documents cùng lúc. Nếu điều kiện khớp với 10 documents thì find() sẽ trả về cả 10 documents đó. Trong khi đó, findOne() chỉ trả về một document đầu tiên khớp với điều kiện tìm kiếm, ngay cả khi có nhiều documents thỏa mãn.

Khi nào nên dùng aggregate() thay vì find()?

find() phù hợp cho những truy vấn cơ bản, ví dụ lọc theo điều kiện, lấy dữ liệu, hoặc kết hợp với sort(), limit().

Tuy nhiên, khi cần thao tác phức tạp hơn như nhóm dữ liệu (group by), tính toán (sum, avg, count…) hay biến đổi dữ liệu thành cấu trúc mới thì ta nên dùng aggregate(). Đây là công cụ mạnh mẽ hơn nhiều, giúp xây dựng các pipeline để xử lý và phân tích dữ liệu ngay trong MongoDB, thay vì phải lấy dữ liệu về và xử lý bằng code bên ngoài.

find() có giới hạn số lượng documents trả về không?

Mặc định, find() sẽ trả về tất cả documents khớp với điều kiện truy vấn. Nếu collection có hàng triệu bản ghi, kết quả có thể rất lớn và ảnh hưởng đến hiệu năng.

Để tránh điều này, ta có thể dùng thêm:

  • limit(n): giới hạn số lượng documents trả về.
  • skip(n): bỏ qua một số documents nhất định trước khi trả về kết quả.

Vì sao find() không dùng index mà tôi đã tạo sẵn?

Không phải mọi truy vấn đều tự động tận dụng index. Có nhiều yếu tố ảnh hưởng đến việc sử dụng index, chẳng hạn như:

  • Thứ tự các trường trong index không khớp với điều kiện truy vấn.
  • Kiểu dữ liệu (data type) giữa index và dữ liệu trong document không đồng nhất.
  • Thiết lập collation (cách so sánh chuỗi) giữa index và truy vấn không phù hợp.

Để kiểm tra, bạn có thể dùng lệnh:

db.collection.find({ ... }).explain("executionStats")

Nếu trong kết quả xuất hiện IXSCAN, nghĩa là MongoDB đang sử dụng index. Ngược lại, nếu thấy COLLSCAN, thì MongoDB đang thực hiện quét toàn bộ collection, dẫn đến hiệu suất chậm hơn.

Con trỏ (cursor) trong MongoDB là gì? Tại sao find() không trả về ngay một mảng kết quả?

Trong MongoDB, khi chạy lệnh find() kết quả không phải là một mảng chứa toàn bộ dữ liệu mà là một cursor (con trỏ). Cursor giống như một “ống dẫn dữ liệu”, cho phép MongoDB trả về tài liệu theo từng lô (batch) thay vì tải hết tất cả cùng lúc. Vì khi làm việc với các collection có hàng nghìn hoặc hàng triệu bản ghi, việc trả về toàn bộ dữ liệu ngay lập tức sẽ tốn nhiều bộ nhớ và làm chậm ứng dụng. Với cursor, MongoDB chỉ lấy dữ liệu khi bạn cần, hay còn gọi là lazy loading – là cơ chế giúp tiết kiệm tài nguyên và tăng hiệu suất.

Tổng kết

Phương thức find() trong MongoDB không chỉ đơn giản là lấy dữ liệu mà còn mang lại nhiều tùy chọn mạnh mẽ để lọc, tìm kiếm và tối ưu hiệu năng truy vấn. Từ các điều kiện cơ bản, toán tử logic cho đến tìm kiếm nâng cao bằng regex và text search, find()  là công cụ thiết yếu mà bất kỳ ai làm việc với MongoDB cũng cần nắm vững. Khi đã quen thuộc với find(), bạn sẽ dễ dàng hơn trong việc phân tích dữ liệu, xây dựng ứng dụng và tối ưu hệ thống. 

ITviec hy vọng bài viết trên đã cung cấp cho bạn những kiến thức bổ ích về find() trong MongoDB.

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.