MongoDB + Python: Từ CRUD cơ bản đến Aggregation “thượng thừa” với PyMongo

Python tutorial - IT technology blog
Python tutorial - IT technology blog

Tại sao MongoDB và Python lại là “cặp bài trùng”?

Nếu bạn đã chán cảnh phải chạy migrate database mỗi khi thay đổi logic ứng dụng, MongoDB chính là cứu cánh. Với cấu trúc Document linh hoạt, nó cho phép bạn lưu trữ dữ liệu dạng JSON-like mà không cần khai báo schema cứng nhắc. Khi kết hợp với Python — ngôn ngữ nổi tiếng với cú pháp tối giản — việc xử lý dữ liệu trở nên mượt mà hơn bao giờ hết.

Qua kinh nghiệm triển khai nhiều dự án, mình thấy PyMongo là driver đáng tin cậy nhất. Nó không chỉ ổn định mà còn hỗ trợ tận răng các tính năng mới nhất của MongoDB. Bài viết này sẽ đi thẳng vào các kỹ thuật thực chiến, từ thao tác cơ bản đến cách tối ưu query cho hàng triệu bản ghi.

Thiết lập môi trường nhanh gọn

Đầu tiên, hãy đảm bảo bạn đã có một instance MongoDB (Docker hoặc MongoDB Atlas). Cài đặt thư viện PyMongo bằng lệnh sau:

pip install "pymongo[srv]"

Mẹo nhỏ: Sử dụng "pymongo[srv]" sẽ tự động cài đặt các phụ thuộc cần thiết để bạn kết nối với cluster trên Cloud (Atlas) mà không lo lỗi DNS.

Triển khai kết nối và xử lý dữ liệu

1. Thiết lập Connection Pool chuẩn

Đừng mở kết nối vô tội vạ. Thay vào đó, hãy đóng gói nó vào một hàm để tái sử dụng connection pool, giúp tiết kiệm tài nguyên server.

from pymongo import MongoClient
from pymongo.errors import ConnectionFailure

def get_database(uri="mongodb://localhost:27017/"):
    try:
        # Timeout sau 5 giây nếu không kết nối được
        client = MongoClient(uri, serverSelectionTimeoutMS=5000)
        client.admin.command('ping')
        return client['dev_database']
    except ConnectionFailure:
        print("Server không phản hồi, kiểm tra lại config!")
        return None

db = get_database()

2. CRUD: Đừng để performance bị nghẽn

Thao tác với PyMongo rất giống khi bạn làm việc với dict trong Python. Tuy nhiên, hiệu suất nằm ở cách bạn gọi hàm.

Insert (Thêm dữ liệu):

collection = db['users']

# Cách làm nghiệp dư: Insert từng record trong vòng lặp
# Cách chuyên nghiệp: Dùng insert_many
users = [
    {"name": "Hoàng", "role": "DevOps", "skills": ["Python", "Docker"]},
    {"name": "An", "role": "Data", "skills": ["SQL", "Python"]}
]
result = collection.insert_many(users)
print(f"Đã chèn {len(result.inserted_ids)} bản ghi.")

Con số thực tế: Trong một test case chèn 10.000 bản ghi, dùng insert_one mất khoảng 15-20 giây. Ngược lại, insert_many chỉ tốn chưa đầy 1 giây. Hãy luôn ưu tiên xử lý theo batch.

Update thông minh: Thay vì ghi đè toàn bộ document, hãy dùng toán tử $set hoặc $push để chỉ cập nhật đúng field cần thiết.

collection.update_one(
    {"name": "Hoàng"},
    {"$push": {"skills": "MongoDB"}}
)

3. Aggregation Pipeline: Xử lý data như một Pro

Aggregation là công cụ mạnh nhất của MongoDB. Nó cho phép bạn tính toán, lọc và biến đổi dữ liệu ngay tại database. Bạn không cần kéo hàng GB dữ liệu về Python rồi mới xử lý bằng Pandas, giúp giảm tải băng thông cực lớn.

Ví dụ: Thống kê số lượng chuyên gia theo từng kỹ năng.

pipeline = [
    {"$unwind": "$skills"}, 
    {"$group": {
        "_id": "$skills",
        "total": {"$sum": 1}
    }},
    {"$sort": {"total": -1}}
]

for stat in collection.aggregate(pipeline):
    print(f"{stat['_id']}: {stat['total']} người")

4. Tuyệt chiêu tối ưu Index

Nhiều hệ thống bị sập CPU chỉ vì thiếu Index. Khi collection vượt ngưỡng 1 triệu bản ghi, một câu query thiếu index sẽ ép database scan toàn bộ ổ cứng.

# Tạo index cho field thường xuyên tìm kiếm
collection.create_index([("role", 1)])

Ngoài ra, hãy sử dụng Projection. Nếu bạn chỉ cần lấy Email, đừng bắt MongoDB trả về cả mảng Bio nặng nề. Thử so sánh: find({}, {"email": 1}) sẽ nhanh hơn 30-50% so với find({}) thông thường.

Kiểm soát và Giám sát (Monitoring)

Làm sao biết query của bạn có đang chạy tối ưu không? Hãy dùng lệnh explain(). Nó sẽ bóc tách chi tiết cách MongoDB thực thi câu lệnh.

stats = collection.find({"role": "DevOps"}).explain()
print(stats['executionStats']['nReturned']) # Số record tìm thấy
print(stats['executionStats']['totalDocsExamined']) # Số record phải quét

Nếu totalDocsExamined lớn gấp nhiều lần nReturned, đó là lúc bạn cần xem lại Index ngay lập tức. Đừng đợi đến khi hệ thống báo lỗi 500 lúc nửa đêm mới bắt đầu tối ưu. Chúc các bạn xây dựng được những hệ thống backend mạnh mẽ!

Share: