Chuyển đến nội dung chính

MongoDB find(): Hướng dẫn hoàn chỉnh cho người mới bắt đầu truy vấn dữ liệu

Hướng dẫn này giải thích cách dùng phương thức find() của MongoDB để truy vấn, lọc, sắp xếp và phân trang dữ liệu với ví dụ thực tế. Hoàn hảo cho người mới và người chuyển từ SQL.
· 9 phút đọc

Khám phá cùng AI

MongoDB là một cơ sở dữ liệu NoSQL phổ biến, được thiết kế cho tính linh hoạt và hiệu năng. Khác với các cơ sở dữ liệu quan hệ truyền thống dùng bảng có cấu trúc và SQL, MongoDB lưu trữ dữ liệu trong các tài liệu linh hoạt giống JSON. Điều này khiến nó lý tưởng cho nhà phát triển làm việc với dữ liệu động hoặc bán cấu trúc.

Một trong những thao tác quan trọng nhất bạn sẽ thực hiện trong MongoDB là truy vấn dữ liệu, và phương thức được dùng thường xuyên nhất là find(). Dù bạn mới bắt đầu hay muốn cải thiện kỹ năng truy vấn, hiểu cách find() hoạt động là điều thiết yếu để điều hướng và thao tác dữ liệu MongoDB hiệu quả.

Hãy tưởng tượng bạn đang khám phá một cơ sở dữ liệu phim. Bạn muốn tìm tất cả phim khoa học viễn tưởng phát hành sau năm 2000, hoặc có thể liệt kê tất cả bình luận người dùng trong một khoảng thời gian cụ thể. Đó là lúc find() phát huy tác dụng.

Trong bài viết này, bạn sẽ học cách dùng phương thức find() để truy xuất tài liệu từ một bộ sưu tập, áp dụng bộ lọc, chiếu trường, sắp xếp kết quả và tối ưu hiệu năng—tất cả với bộ dữ liệu sample_mflix.

Để xem hướng dẫn thực hành sử dụng MongoDB trong quy trình khoa học dữ liệu, hãy xem khóa học Introduction to MongoDB in Python.

Giới thiệu về find() trong MongoDB

Phương thức find() là cách chính của MongoDB để truy xuất tài liệu từ một bộ sưu tập. Nó trả về một con trỏ (cursor) trỏ tới các tài liệu khớp với truy vấn đã chỉ định. Sau đó, bạn có thể lặp qua con trỏ này, hoặc chuyển nó thành một mảng, tùy theo nhu cầu ứng dụng của bạn.

Cú pháp đầy đủ như sau:

db.collection.find(<query>, <projection>, <options>)

Tham số

  • query (tùy chọn): Một tài liệu xác định tiêu chí chọn tài liệu. Nếu bạn bỏ qua tham số này hoặc truyền một tài liệu trống ({}), find() sẽ trả về tất cả tài liệu trong bộ sưu tập.
  • projection (tùy chọn): Một tài liệu chỉ định những trường nào sẽ được trả về. Bạn có thể bao gồm (1) hoặc loại trừ (0) trường. Mặc định, MongoDB trả về tất cả trường, bao gồm _id.
  • options (tùy chọn): Một tài liệu bổ sung để cấu hình hành vi truy vấn. Bạn có thể đặt các thuộc tính như thứ tự sắp xếp, giới hạn phân trang, thời gian chờ và gợi ý chỉ mục.

Ví dụ

Để các khái niệm rõ ràng và dễ áp dụng nhất, chúng ta sẽ dùng bộ dữ liệu mẫu MongoDB Atlas—cụ thể là cơ sở dữ liệu sample_mflix. Bộ dữ liệu này gồm các bộ sưu tập như movies, comments, users và theaters, mà chúng ta sẽ tham chiếu xuyên suốt bài viết để minh họa từng ví dụ.

db.books.find(
  { year: { $gte: 2000 } },              // query: books published in or after 2000
  { title: 1, author: 1, _id: 0 },        // projection: include only title and author
  { sort: { title: 1 }, limit: 5 }       // options: sort by title, return only 5 books
)

Truy vấn này:

  • Lọc các sách xuất bản từ năm 2000 trở đi.
  • Chỉ trả về trường title và author (loại trừ _id).
  • Sắp xếp kết quả theo tiêu đề theo thứ tự chữ cái (A–Z).
  • Giới hạn đầu ra ở 5 cuốn sách khớp đầu tiên.

Truy vấn này tìm các phim phát hành sau năm 2000, sắp xếp theo tiêu đề và giới hạn ở năm kết quả.

Hình 1. Truy vấn này tìm các phim phát hành sau năm 2000, sắp xếp theo tiêu đề và giới hạn ở năm kết quả.

Kết quả là một con trỏ, tải lười (lazy) các tài liệu khớp khi bạn lặp qua chúng. Bạn có thể dùng .toArray() hoặc .forEach() để xử lý kết quả.

Nếu bạn kết nối qua shell MongoDB (mongosh) và không gán kết quả cho biến, mặc định chỉ 20 tài liệu đầu tiên sẽ hiển thị. Để xem thêm, nhập nó rồi nhấn Enter. Quá trình này sẽ tiếp tục lấy lô tiếp theo từ con trỏ.

> Lưu ý: Trên đây là hành vi riêng của shell, không phải hạn chế của MongoDB. Trong hầu hết driver MongoDB (ví dụ, Node.js, Python, Java), gọi .toArray() hoặc lặp qua con trỏ sẽ tự động lấy tất cả kết quả khớp. Hành vi con trỏ có thể khác nhau tùy môi trường và ngôn ngữ bạn dùng.

Truy vấn này truy xuất tất cả phim, chỉ trả về trường _id và dùng con trỏ để lấy thêm kết quả.

Hình 2. Truy vấn này truy xuất tất cả phim, chỉ trả về trường _id và dùng con trỏ để lấy thêm kết quả.

Nếu bạn mới với cơ sở dữ liệu NoSQL, khóa học Introduction to NoSQL cung cấp các khái niệm nền tảng bổ trợ cho hướng dẫn này.

Viết các truy vấn đầu tiên của bạn

Tiếp tục với ví dụ phim, giả sử bạn muốn lấy tất cả phim thuộc thể loại Sci-Fi. Cách làm như sau:

db.movies.find({ genres: "Sci-Fi" })

Bây giờ, bạn chỉ muốn các phim phát hành từ năm 2000 trở đi:

db.movies.find({ year: { $gte: 2000 } })

Vậy nếu muốn cả hai điều kiện? Hãy kết hợp điều kiện:

db.movies.find({ genres: "Sci-Fi", year: { $gte: 2000 } })

Hoặc, bạn có thể viết tường minh bằng $and:

db.movies.find({ $and: [ { genres: "Sci-Fi" }, { year: { $gte: 2000 } } ] })

Cả hai truy vấn trả về cùng kết quả—các tài liệu có thể loại Sci-Fi và năm phát hành từ 2000 trở đi. Dạng đầu ngắn gọn hơn và được ưa dùng khi các trường khác nhau.

Các cách viết truy vấn khác nhau này cho ra cùng một kết quả.

Hình 3. Các cách viết truy vấn khác nhau này cho ra cùng một kết quả.

Hãy dùng find() như một phễu lọc—mỗi điều kiện sẽ thu hẹp kết quả của bạn.

Sử dụng toán tử so sánh và logic

Giờ hãy xem cách dùng các toán tử so sánh và logic để đưa truy vấn của bạn lên tầm tiếp theo.

Toán tử so sánh

  • $eq, $ne: bằng, không bằng
  • $gt, $gte, $lt, $lte: lớn hơn/nhỏ hơn (hoặc bằng)
  • $in, $nin: thuộc hoặc không thuộc mảng

Hãy tưởng tượng bạn đang theo dõi đơn hàng khách hàng và muốn các đơn trong khoảng 100–500 USD:

db.orders.find({ total: { $gte: 100, $lte: 500 } })

Toán tử logic

  • $and, $or, $nor, $not

Giờ, giả sử bạn muốn hiển thị sản phẩm trong danh mục "Books" hoặc có giá dưới 20 USD. Dưới đây là một truy vấn ví dụ:

db.products.find({ $or: [ { category: "Books" }, { price: { $lt: 20 } } ] })

Truy vấn trường lồng nhau

Bạn có thể truy cập trường lồng nhau bằng cú pháp chấm:

db.customers.find({ "address.city": "Chicago" })

Chiếu trường (Projection)

Projection cho phép bạn chỉ trả về những gì cần thiết, từ đó cải thiện đáng kể hiệu năng và độ rõ ràng.

db.movies.find({}, { directors: 1, languages: 1, _id: 0 })

Quan trọng: Khi chỉ định projection, MongoDB yêu cầu bạn chọn giữa hai chế độ: Hoặc bao gồm các trường cụ thể (1) hoặc loại trừ các trường cụ thể (0). Bạn không thể trộn cả hai kiểu trong cùng một projection, ngoại trừ trường _id, có thể loại trừ ngay cả khi đang bao gồm các trường khác.

✅ Hợp lệ (chỉ bao gồm):

db.movies.find({}, { directors: 1, languages: 1 })

✅ Hợp lệ (chỉ loại trừ):

db.movies.find({}, { directors: 0, _id: 0 })

❌ Không hợp lệ (trộn bao gồm và loại trừ):

db.movies.find({}, { directors: 1, languages: 0 }) // Not allowed

Ngoại lệ duy nhất là trường _id, bạn có thể loại trừ ngay cả khi đang bao gồm các trường khác:

db.movies.find({}, { directors: 1, _id: 0 }) // Allowed

Ví dụ này minh họa lỗi khi trộn bao gồm và loại trừ trường trong một projection, điều MongoDB không cho phép, ngoại trừ _id.

Hình 4. Ví dụ này minh họa lỗi khi trộn bao gồm và loại trừ trường trong một projection, điều MongoDB không cho phép, ngoại trừ `_id`.

Sắp xếp, giới hạn và bỏ qua kết quả

Khi làm việc với bộ dữ liệu lớn, hiển thị cho người dùng chỉ một phần dữ liệu mỗi lần là điều thường gặp—kỹ thuật này gọi là phân trang. Ví dụ, một ứng dụng streaming có thể hiển thị 10 phim mỗi trang thay vì tải hàng nghìn phim cùng lúc. Điều này không chỉ cải thiện hiệu năng mà còn nâng cao trải nghiệm người dùng.

Trong MongoDB, phân trang được thực hiện bằng cách kết hợp sort(), limit() và skip().

SQL vs. MongoDB: So sánh cơ bản

Nếu bạn đến từ nền tảng SQL, dưới đây là cách các khái niệm phân trang phổ biến tương ứng:

Truy vấn cơ bản trong SQL: 

SELECT * FROM movies WHERE year >= 2000

Tương tự trong MongoDB như sau:

db.movies.find({ year: { $gte: 2000 } })

Chọn trường trong SQL: 

SELECT title, year FROM movies

Chọn trường trong MongoDB: 

db.movies.find({}, { title: 1, year: 1, _id: 0 })

Giờ, hãy xem thêm vài ví dụ về các thao tác truy vấn phổ biến:

Sắp xếp:

  • SQL: ORDER BY title ASC
  • MongoDB: .sort({ title: 1 })

Giới hạn:

  • SQL: LIMIT 10
  • MongoDB: .limit(10)

Bỏ qua:

  • SQL: OFFSET 10
  • MongoDB: .skip(10)

Hiểu những điểm tương đồng này có thể giúp người dùng SQL chuyển sang hệ thống truy vấn dựa trên tài liệu của MongoDB dễ dàng hơn.

Sắp xếp kết quả

Sắp xếp giúp bạn tổ chức kết quả theo một trường cụ thể—ví dụ, sắp xếp phim theo tiêu đề theo thứ tự bảng chữ cái:

db.movies.find().sort({ title: 1 }) // ascending (A–Z)
  • 1 nghĩa là thứ tự tăng dần (A–Z, 0–9)
  • -1 nghĩa là thứ tự giảm dần (Z–A, 9–0)

Giới hạn kết quả

Chỉ trả về một số lượng kết quả nhất định—ví dụ, 10 kết quả đầu tiên:

db.movies.find().limit(10)

Điều này hữu ích khi triển khai các danh sách “Top 10” hoặc màn hình ban đầu.

Bỏ qua kết quả

Dùng để bỏ qua các tài liệu. Thường kết hợp với limit() cho phân trang:

db.movies.find().skip(10).limit(10)

Truy vấn này bỏ qua 10 tài liệu đầu và trả về 10 tài liệu tiếp theo, hiệu quả hiển thị Trang 2 khi pageSize = 10.

Công thức phân trang:

skip = (pageNumber - 1) * pageSize

> Lưu ý: Dùng skip() cho phân trang sâu (ví dụ, skip(10000)) có thể kém hiệu quả, vì MongoDB vẫn quét và loại bỏ các tài liệu bị bỏ qua. Để hiệu năng tốt hơn với bộ dữ liệu lớn, hãy cân nhắc phân trang theo phạm vi dựa trên các trường được lập chỉ mục như _id.

Ví dụ kết hợp: Trang 3, 10 phim mỗi trang, sắp xếp theo tiêu đề

db.movies.find()
  .sort({ title: 1 })
  .skip(20)
  .limit(10)

Mẫu này cũng hữu ích cho các tính năng như “tải thêm” hoặc cuộn vô hạn.

Truy vấn này truy xuất trang thứ ba của kết quả (10 phim mỗi trang), sắp xếp theo tiêu đề.

Hình 5. Truy vấn này truy xuất trang thứ ba của kết quả (10 phim mỗi trang), sắp xếp theo tiêu đề.

Làm việc với con trỏ (Cursor)

Phương thức find() trả về một con trỏ—một con trỏ tới tập kết quả. Bạn có thể lặp qua nó:

const cursor = db.movies.find({ type: "movie" });
cursor.forEach(doc => print(doc.name));

Hoặc chuyển nó thành mảng:

const results = db.collection.find().toArray();

Sau khi gọi find(), bạn nhận được một con trỏ. Bạn có thể dùng nó để lặp qua tài liệu hoặc chuyển toàn bộ tập kết quả thành mảng.

Hình 6. Sau khi gọi `find()`, bạn nhận được một con trỏ. Bạn có thể dùng nó để lặp qua tài liệu hoặc chuyển toàn bộ tập kết quả thành mảng.

> Lưu ý: Đối tượng con trỏ có sẵn trong mongosh hoặc trong mã qua các driver (ví dụ, Node.js, Python). Các công cụ GUI như MongoDB Compass xử lý con trỏ nội bộ. Bạn sẽ không truy cập hoặc lặp qua chúng trực tiếp.

Tùy chọn nâng cao cho con trỏ

Để tinh chỉnh truy vấn, bạn có thể dùng thêm các phương thức trên con trỏ:

db.users.find({ age: { $gte: 30 } })
  .sort({ name: 1 })
  .limit(10)
  .skip(5)
  .maxTimeMS(200)
  .hint({ age: 1 })
  • maxTimeMS(ms): hủy truy vấn nếu mất quá nhiều thời gian
  • hint(): buộc MongoDB dùng một chỉ mục cụ thể
  • batchSize(n): kiểm soát số tài liệu mỗi lô

Đang chuẩn bị phỏng vấn? Khám phá Top 25 câu hỏi phỏng vấn MongoDB kèm lời giải để kiểm tra mức hiểu biết của bạn về các khái niệm cốt lõi như find().

Chỉ mục và tối ưu hiệu năng

Chỉ mục giống như “phao cứu trợ”—chúng giúp MongoDB truy xuất tài liệu nhanh hơn.

Tạo chỉ mục

db.movies.createIndex({ type: 1 })

Sử dụng .explain()

db.users.find({ type: "movie" }).explain()

Điều này cho biết liệu các chỉ mục có được sử dụng không và truy vấn của bạn hiệu quả thế nào.

Không có chỉ mục: Chạy explain() cho truy vấn trước khi lập chỉ mục.

Hình 7. Chạy `explain()` cho truy vấn trước khi lập chỉ mục.

Tạo chỉ mục: 

Tạo một chỉ mục đơn giản trên trường type.

Hình 8. Tạo một chỉ mục đơn giản trên trường type.

Có chỉ mục: 

Chạy lại truy vấn với explain(), lúc này dùng chỉ mục.

Hình 9. Chạy lại truy vấn với `explain()`, lúc này dùng chỉ mục.

Bạn có thể thấy giai đoạn trước đây là CollScan (quét bộ sưu tập) đã trở thành Fetch + IXSCAN, về cơ bản là dùng chỉ mục + tìm thêm dữ liệu không nằm trong chỉ mục (vì chúng ta đang trả về mọi thứ).

Dùng MongoDB Compass để trực quan hóa truy vấn

Nếu bạn thích công cụ trực quan, MongoDB Compass là ứng dụng GUI chính thức. Nó cho phép bạn:

  • Xây dựng truy vấn trực quan bằng bộ lọc.
  • Xem trước tài liệu và chỉ mục.
  • Tối ưu và xác thực hiệu năng truy vấn.

Bạn thậm chí có thể chuyển sang "JSON view" để sao chép/dán truy vấn vào mã của bạn.

Dùng Atlas + AI để tạo truy vấn

Nếu bạn đang dùng MongoDB Atlas, bạn có thể tận dụng Atlas Search với trình tạo truy vấn hỗ trợ AI.

Chỉ cần nhập lời nhắc ngôn ngữ tự nhiên như:

"Hiển thị tất cả khách hàng từ Brazil đã đặt đơn trên 100 USD trong tháng trước."

Atlas sẽ gợi ý một truy vấn MongoDB phù hợp bằng find() hoặc $search. Đây là một công cụ tăng năng suất tuyệt vời, đặc biệt khi bạn chưa chắc về cú pháp.

Dùng Compass để tạo truy vấn từ ngôn ngữ tự nhiên.

Hình 10. Dùng Compass để tạo truy vấn từ ngôn ngữ tự nhiên.

Những lỗi thường gặp và cách tránh

  • Truy vấn các trường chưa lập chỉ mục → Dẫn tới hiệu năng chậm
  • Tập kết quả lớn → Dùng phân trang (limit + skip)
  • Projection kém hiệu quả → Luôn chỉ chiếu những trường cần thiết
  • Không khớp kiểu dữ liệu → Đảm bảo kiểu trường khớp nhau (ví dụ, số vs. chuỗi)
  • Trộn bao gồm và loại trừ trong projection → Không được phép, ngoại trừ _id

So sánh MongoDB find() với SQL

Nếu bạn đến từ nền tảng SQL, hiểu cách find() của MongoDB so với các truy vấn SQL quen thuộc có thể giúp quá trình chuyển đổi dễ dàng hơn. Dưới đây là so sánh tổng quan các thao tác phổ biến:

Khái niệm

Ví dụ SQL

Tương đương trong MongoDB

Truy vấn cơ bản

SELECT * FROM users WHERE age >= 18

db.users.find({ age: { $gte: 18 } })

Chọn trường

SELECT name, email FROM users

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

Sắp xếp

ORDER BY created_at DESC

.sort({ createdAt: -1 })

Giới hạn

LIMIT 10

.limit(10)

Bỏ qua

OFFSET 10

.skip(10)

Như bạn thấy, cú pháp của MongoDB giống JavaScript hơn và diễn đạt theo định dạng JSON, trong khi SQL mang tính khai báo và có cấu trúc. Biết những điểm tương đồng này sẽ giúp bạn nhanh chóng cảm thấy quen thuộc với MongoDB.

Tóm tắt và thực tiễn tốt

  • Hãy coi find() là bộ lọc dữ liệu của bạn—kết hợp điều kiện để có kết quả chính xác.
  • Luôn chỉ chiếu các trường cần thiết để rõ ràng và hiệu năng tốt.
  • Dùng chỉ mục để tránh quét chậm.
  • Dùng Compass nếu bạn thích phản hồi trực quan và lặp nhanh hơn.
  • Tận dụng trợ lý AI của MongoDB Atlas để tạo truy vấn nhanh hơn.
  • Khám phá các tùy chọn con trỏ nâng cao để kiểm soát chi tiết kết quả.

Dù bạn đang xây dựng bảng điều khiển cho startup, công cụ phân tích dữ liệu hay back end thương mại điện tử, thành thạo find() sẽ giúp trải nghiệm MongoDB của bạn mượt mà, nhanh và hiệu quả hơn.

Hãy đón đọc thêm các bài viết về MongoDB trong thời gian tới!

Muốn xác nhận kỹ năng MongoDB của bạn? Đọc Hướng dẫn toàn diện về chứng chỉ MongoDB để tìm hiểu lựa chọn phù hợp.

FAQs

Về mặt thực tế, con trỏ là gì? Tại sao find() không trả về một mảng kết quả?

MongoDB trả về một con trỏ, tải tài liệu theo lô (lazy loading). Điều này hiệu quả cho các bộ sưu tập lớn nhưng có thể gây bối rối cho người mới, những người mong đợi một mảng.

Tại sao find() có ba tham số, nhưng tôi thường chỉ thấy hai? Vì sao đối số thứ ba (options) hiếm khi xuất hiện trong ví dụ?

Bởi vì hầu hết nhà phát triển dùng các phương thức xâu chuỗi như .sort(), .limit() và .skip() thay vì truyền chúng trong đối tượng options. Một số tính năng (ví dụ, hint, readConcern, collation) yêu cầu dùng tài liệu options hoặc gọi phương thức riêng.

Tại sao find() không dùng chỉ mục tôi đã tạo? Tôi đã tạo chỉ mục nhưng truy vấn find() vẫn chậm.

Không phải mọi truy vấn đều tự động dùng chỉ mục. Thứ tự trường, kiểu dữ liệu và cài đặt collation có thể ảnh hưởng việc sử dụng chỉ mục. Dùng .explain("executionStats") để kiểm tra MongoDB có dùng chỉ mục (IXSCAN) hay quét toàn bộ (COLLSCAN).

Projection của tôi có thực sự cải thiện hiệu năng không? Tôi chỉ chiếu hai trường nhưng có vẻ không nhanh hơn.

Nếu các trường được chiếu không nằm trong một chỉ mục, MongoDB vẫn cần tải toàn bộ tài liệu từ đĩa. Hãy dùng chỉ mục bao phủ (covering index) để phục vụ truy vấn hoàn toàn từ chỉ mục, tránh tra cứu tài liệu.

Tôi có thể dùng find() để nhóm hoặc tổng hợp dữ liệu không? Tôi có thể đếm số phim phát hành mỗi năm bằng find() không?

Không. find() dùng để truy xuất tài liệu. Để nhóm hoặc tổng hợp, hãy dùng Aggregation Framework ($group, $count, v.v.). Mặc dù find() + .countDocuments() xử lý đếm cơ bản, các nhu cầu phân tích thực sự nên dùng .aggregate().

Sự khác nhau giữa find() và findOne() trong MongoDB là gì?

 find() trả về một con trỏ tới tất cả tài liệu khớp, trong khi indOne() trả về trực tiếp tài liệu khớp đầu tiên. Dùng findOne() khi bạn chỉ cần một kết quả.

Làm sao truy vấn một trường mảng bằng find() trong MongoDB?

Bạn có thể khớp phần tử mảng trực tiếp. Ví dụ, { tags: "tech" } khớp nếu "tech" nằm trong mảng tags. Dùng $elemMatch cho các điều kiện phức tạp hơn.

Tôi có thể dùng regular expression trong truy vấn find() của MongoDB không?

Có, MongoDB hỗ trợ biểu thức chính quy với cú pháp như { title: /sci-fi/i } để thực hiện khớp mẫu không phân biệt hoa thường trên chuỗi.

Làm sao dùng find() với khoảng thời gian ngày tháng trong MongoDB?

Hãy dùng đối tượng ISODate trong truy vấn. Ví dụ: { createdAt: { $gte: ISODate("2024-01-01"), $lte: ISODate("2024-12-31") } }.

Tác động hiệu năng khi dùng find() mà không có chỉ mục là gì?

Truy vấn trên các trường chưa lập chỉ mục sẽ kích hoạt quét toàn bộ bộ sưu tập, điều này chậm với bộ dữ liệu lớn. Luôn lập chỉ mục cho các trường được truy vấn thường xuyên.

Samuel Molling

Tác giả

Samuel Molling

Samuel Molling là Kỹ sư Độ tin cậy Cơ sở dữ liệu, đam mê tự động hóa và mở rộng hạ tầng dữ liệu an toàn trên đám mây. 

Chủ đề

Liên quan