Mongoose giới hạn / bù đắp và truy vấn đếm


84

Có một chút kỳ lạ về hiệu suất truy vấn ... Tôi cần chạy một truy vấn có tổng số tài liệu và cũng có thể trả về một tập kết quả có thể được giới hạn và bù đắp.

Vì vậy, tôi có tổng cộng 57 tài liệu và người dùng muốn 10 tài liệu bù lại 20.

Tôi có thể nghĩ ra 2 cách để làm điều này, đầu tiên là truy vấn cho tất cả 57 tài liệu (trả về dưới dạng mảng), sau đó sử dụng array.slice trả về tài liệu mà họ muốn. Tùy chọn thứ hai là chạy 2 truy vấn, truy vấn đầu tiên sử dụng phương pháp 'đếm' gốc của mongo, sau đó chạy truy vấn thứ hai bằng cách sử dụng trình tổng hợp $ gốc và $ bỏ qua của mongo.

Bạn nghĩ cái nào sẽ mở rộng quy mô tốt hơn? Thực hiện tất cả trong một truy vấn hay chạy hai truy vấn riêng biệt?

Biên tập:

// 1 query
var limit = 10;
var offset = 20;

Animals.find({}, function (err, animals) {
    if (err) {
        return next(err);
    }

    res.send({count: animals.length, animals: animals.slice(offset, limit + offset)});
});


// 2 queries
Animals.find({}, {limit:10, skip:20} function (err, animals) {            
    if (err) {
        return next(err);
    }

    Animals.count({}, function (err, count) {
        if (err) {
            return next(err);
        }

        res.send({count: count, animals: animals});
    });
});

Tôi không chắc về Mongoose tuy nhiên count()chức năng mặc định trong PHP không tính đến limithoặc không tính skipđến trừ khi được yêu cầu như vậy chỉ cần chạy một truy vấn giới hạn và bỏ qua và sau đó nhận được số lượng sẽ đưa ra giải pháp hiệu quả nhất ở đây có lẽ. Tuy nhiên, làm thế nào bạn sẽ biết có 57 tài liệu nếu bạn không thực hiện hai truy vấn để đếm những gì hiện có ở đó? Bạn có một số tĩnh không bao giờ thay đổi? Nếu không, bạn sẽ cần phải thực hiện cả bỏ qua và giới hạn sau đó đếm.
Sammaye

Xin lỗi, tôi đang nói về việc sử dụng phương pháp đếm có nguồn gốc Mông Cổ củadb.collection.find(<query>).count();
leepowell

Xin lỗi là tôi, tôi đã đọc nhầm câu hỏi của bạn. Hmmm thực sự tôi không chắc cái nào sẽ tốt hơn, liệu bộ kết quả của bạn có thực sự thấp như 57 tài liệu không? Nếu vậy thì lát phía máy khách có thể hiệu suất hơn mili giây.
Sammaye

Tôi đã thêm ví dụ vào câu hỏi ban đầu, tôi không nghĩ dữ liệu sẽ cao đến hơn 10.000 người nhưng có khả năng nó có thể.
leepowell

Ở 10k bản ghi, bạn có thể thấy việc xử lý bộ nhớ của JS kém hiệu quả hơn count()chức năng của MongoDB. Các count()chức năng trong MongoDB là tương đối chậm nhưng nó vẫn còn khá nhiều càng nhanh càng tốt hầu hết các biến thể phía client trên bộ lớn hơn và nó có thể là nhanh hơn so với bên đếm khách hàng ở đây có thể. Nhưng phần đó là chủ quan của bạn để kiểm tra. Xin lưu ý bạn, tôi đã đếm dễ dàng 10k mảng độ dài trước đây nên nó có thể ở phía máy khách nhanh hơn, rất khó để nói ở 10k phần tử.
Sammaye

Câu trả lời:


129

Tôi đề nghị bạn sử dụng 2 truy vấn:

  1. db.collection.count()sẽ trả về tổng số mục. Giá trị này được lưu trữ ở đâu đó trong Mongo và nó không được tính toán.

  2. db.collection.find().skip(20).limit(10)ở đây tôi giả sử bạn có thể sử dụng sắp xếp theo trường nào đó, vì vậy đừng quên thêm chỉ mục vào trường này. Truy vấn này cũng sẽ nhanh chóng.

Tôi nghĩ rằng bạn không nên truy vấn tất cả các mục và thực hiện bỏ qua và lấy, vì sau này khi bạn có dữ liệu lớn, bạn sẽ gặp vấn đề với việc truyền và xử lý dữ liệu.


1
Những gì tôi đang viết chỉ là một bình luận mà không có bất kỳ sự chú ý nào nhưng tôi đã nghe nói rằng .skip()lệnh này rất nặng đối với CPU vì nó đi đến đầu bộ sưu tập và nhận được giá trị được chỉ định trong tham số của .skip(). Nó có thể có tác động thực sự đến bộ sưu tập lớn! Nhưng tôi không biết cái nào là nặng nhất giữa việc sử dụng .skip()hay lấy toàn bộ bộ sưu tập và cắt bằng JS ... Bạn nghĩ sao?
Zachary Dahan

2
@Stuffix Tôi đã nghe những lo lắng tương tự về việc sử dụng .skip(). Câu trả lời này đề cập đến nó và khuyên bạn nên sử dụng bộ lọc trên trường ngày. Người ta có thể sử dụng điều này với.skip() & .take()phương pháp. Đây có vẻ là một ý kiến ​​hay. Tuy nhiên, tôi đang gặp khó khăn với câu hỏi của OP này về cách tính tổng số tài liệu. Nếu một bộ lọc được sử dụng để chống lại các tác động của hiệu suất .skip(), làm thế nào chúng ta có thể có một con số chính xác? Số lượng được lưu trữ trong db sẽ không phản ánh tập dữ liệu đã lọc của chúng tôi.
Michael Leanos

Xin chào @MichaelLeanos, tôi đang gặp phải vấn đề tương tự: tức là làm thế nào để có được tổng số tài liệu. Nếu bộ lọc được sử dụng thì làm thế nào chúng ta có thể có số lượng chính xác? Bạn đã nhận được giải pháp cho điều này?
virsha

@virsha, sử dụng cursor.count()để trả về số lượng documnet đã lọc (nó sẽ không thực thi truy vấn mà sẽ trả về cho bạn số lượng tài liệu phù hợp). Đảm bảo rằng bạn lọc và sắp xếp các thuộc tính được lập chỉ mục và mọi thứ sẽ ổn.
user854301

@virsha Sử dụng cursor.count()phải hoạt động như @ user854301 đã chỉ ra. Tuy nhiên, những gì tôi cuối cùng đã làm là thêm một điểm cuối vào API của mình ( /api/my-colllection/stats) mà tôi đã sử dụng để trả về các số liệu thống kê khác nhau về bộ sưu tập của mình bằng cách sử dụng db.collection.stats của Mongoose tính năng . Vì tôi thực sự chỉ cần điều này cho giao diện người dùng của mình, tôi chỉ truy vấn điểm cuối để trả về thông tin đó độc lập với phân trang phía máy chủ của tôi.
Michael Leanos

19

Thay vì sử dụng 2 truy vấn riêng biệt, bạn có thể sử dụng aggregate()trong một truy vấn duy nhất:

Tổng hợp "$ facet" có thể được tìm nạp nhanh hơn, Tổng sốDữ liệu có bỏ qua & giới hạn

    db.collection.aggregate([

      //{$sort: {...}}

      //{$match:{...}}

      {$facet:{

        "stage1" : [ {"$group": {_id:null, count:{$sum:1}}} ],

        "stage2" : [ { "$skip": 0}, {"$limit": 2} ]
  
      }},
     
     {$unwind: "$stage1"},
  
      //output projection
     {$project:{
        count: "$stage1.count",
        data: "$stage2"
     }}

 ]);

đầu ra như sau: -

[{
     count: 50,
     data: [
        {...},
        {...}
      ]
 }]

Ngoài ra, hãy xem tại https://docs.mongodb.com/manual/reference/operator/aggregation/facet/


2

Sau khi phải tự mình giải quyết vấn đề này, tôi muốn dựa trên câu trả lời của người dùng854301.

Mongoose ^ 4.13.8 Tôi đã có thể sử dụng một hàm được gọi là toConstructor() cho phép tôi tránh tạo truy vấn nhiều lần khi áp dụng bộ lọc. Tôi biết chức năng này cũng có sẵn trong các phiên bản cũ hơn nhưng bạn sẽ phải kiểm tra tài liệu Mongoose để xác nhận điều này.

Những điều sau đây sử dụng Bluebird hứa hẹn:

let schema = Query.find({ name: 'bloggs', age: { $gt: 30 } });

// save the query as a 'template'
let query = schema.toConstructor();

return Promise.join(
    schema.count().exec(),
    query().limit(limit).skip(skip).exec(),

    function (total, data) {
        return { data: data, total: total }
    }
);

Bây giờ truy vấn đếm sẽ trả về tổng số bản ghi mà nó khớp và dữ liệu trả về sẽ là một tập hợp con của tổng số bản ghi.

Vui lòng lưu ý () xung quanh truy vấn () tạo truy vấn.



0
db.collection_name.aggregate([
    { '$match'    : { } },
    { '$sort'     : { '_id' : -1 } },
    { '$facet'    : {
        metadata: [ { $count: "total" } ],
        data: [ { $skip: 1 }, { $limit: 10 },{ '$project' : {"_id":0} } ] // add projection here wish you re-shape the docs
    } }
] )

Thay vì sử dụng hai truy vấn để tìm tổng số và bỏ qua bản ghi phù hợp.
$ facet là cách tốt nhất và được tối ưu hóa.

  1. Phù hợp với kỷ lục
  2. Tìm total_count
  3. bỏ qua hồ sơ
  4. Và cũng có thể định hình lại dữ liệu theo nhu cầu của chúng tôi trong truy vấn.

1
Vui lòng thêm một số lời giải thích cho câu trả lời của bạn để những người khác có thể học hỏi từ nó
Nico Haase,
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.