最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

MongoDB排序與分頁之sort()和limit()的組合用法示例詳解(實(shí)踐指南)

 更新時間:2025年10月28日 10:22:07   作者:Jinkxs  
sort()和limit()是MongoDB中實(shí)現(xiàn)有序數(shù)據(jù)展示的基石,本文將帶你深入探索MongoDB中sort()和limit()的組合藝術(shù),結(jié)合Java代碼實(shí)戰(zhàn),教你如何高效、安全地實(shí)現(xiàn)數(shù)據(jù)的有序分頁,感興趣的朋友跟隨小編一起看看吧

MongoDB排序與分頁:sort()和limit()的組合用法

在構(gòu)建現(xiàn)代 Web 應(yīng)用、移動應(yīng)用或 API 服務(wù)時,數(shù)據(jù)的展示往往不是“全量返回”那么簡單。用戶期望看到的是有序的、分批次加載的內(nèi)容,比如“最新的10條訂單”、“按價格排序的商品列表”或“第3頁的用戶評論”。這正是 MongoDB 中 sort()limit() 方法大顯身手的場景。

然而,看似簡單的排序與分頁,背后卻隱藏著巨大的性能陷阱。一個不當(dāng)?shù)?sort() 可能導(dǎo)致內(nèi)存耗盡,一次無索引的分頁查詢可能讓數(shù)據(jù)庫 CPU 爆表。本文將帶你深入探索 MongoDB 中 sort()limit() 的組合藝術(shù),結(jié)合 Java 代碼實(shí)戰(zhàn),教你如何高效、安全地實(shí)現(xiàn)數(shù)據(jù)的有序分頁,讓你的應(yīng)用流暢如絲!

基礎(chǔ)回顧:sort()與limit()是什么?

在 MongoDB 中,sort()limit() 是游標(biāo)(Cursor)上的鏈?zhǔn)椒椒?,用于控制查詢結(jié)果的順序和數(shù)量。

sort():定義結(jié)果的排序順序

sort() 接收一個文檔作為參數(shù),指定按哪些字段排序以及排序方向。

  • 1:升序(Ascending)
  • -1:降序(Descending)

JavaScript 示例:

// 按創(chuàng)建時間降序(最新在前)
db.posts.find().sort({ createdAt: -1 })
// 按價格升序,再按評分降序
db.products.find().sort({ price: 1, rating: -1 })

limit():限制返回的文檔數(shù)量

limit() 接收一個整數(shù),指定最多返回多少個文檔。

// 只返回前5個文檔
db.users.find().limit(5)

組合使用:實(shí)現(xiàn)分頁的基礎(chǔ)

最常見的組合是 sort().limit(),用于獲取“前 N 條”有序數(shù)據(jù)。

// 獲取按時間排序的最新10篇文章
db.articles.find().sort({ publishTime: -1 }).limit(10)

Java 中的實(shí)現(xiàn):使用 MongoDB Java Driver

讓我們通過 Java 代碼來實(shí)踐這些操作。

環(huán)境準(zhǔn)備

首先,確保你已添加 MongoDB Java Driver 依賴(以 Maven 為例):

<dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongodb-java-driver</artifactId>
    <version>4.10.2</version>
</dependency>

基礎(chǔ)排序與限制

import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Sorts;
import org.bson.Document;
import java.util.ArrayList;
import java.util.List;
public class SortLimitBasic {
    public static void main(String[] args) {
        // 1. 創(chuàng)建客戶端
        MongoClient mongoClient = MongoClients.create("mongodb://localhost:27017");
        // 2. 獲取集合
        MongoDatabase database = mongoClient.getDatabase("blogdb");
        MongoCollection<Document> posts = database.getCollection("posts");
        // 3. 構(gòu)建查詢:獲取最新的5篇文章
        List<Document> latestPosts = new ArrayList<>();
        posts.find()                                  // 查詢所有
              .sort(Sorts.descending("createdAt"))    // 按創(chuàng)建時間降序
              .limit(5)                               // 限制5條
              .into(latestPosts);                     // 結(jié)果存入列表
        // 4. 輸出
        for (Document post : latestPosts) {
            System.out.println(post.toJson());
        }
        mongoClient.close();
    }
}

復(fù)合排序

// 按類別分組,同類中按點(diǎn)贊數(shù)降序,再按發(fā)布時間降序
List<Document> sortedPosts = new ArrayList<>();
posts.find(Filters.eq("category", "tech"))
     .sort(Sorts.orderBy(
         Sorts.ascending("category"),
         Sorts.descending("likes"),
         Sorts.descending("createdAt")
     ))
     .limit(10)
     .into(sortedPosts);

分頁的陷阱:skip()的性能問題

實(shí)現(xiàn)分頁最直觀的方式是使用 skip() 方法:

// 第1頁:跳過0條,取10條
db.posts.find().sort({createdAt: -1}).skip(0).limit(10)
// 第2頁:跳過10條,取10條
db.posts.find().sort({createdAt: -1}).skip(10).limit(10)
// 第100頁:跳過990條,取10條
db.posts.find().sort({createdAt: -1}).skip(990).limit(10)

為什么skip()很危險?

隨著 skip() 的值增大,性能會急劇下降。原因如下:

  1. 必須掃描前 N 條數(shù)據(jù):即使你只想取第100頁的10條數(shù)據(jù),MongoDB 也必須先掃描并跳過前990條。
  2. 無法利用索引優(yōu)勢:如果排序字段有索引,skip(0) 可能很快,但 skip(990) 仍然需要遍歷索引的前990個條目。
  3. 內(nèi)存和CPU消耗大:大量數(shù)據(jù)被加載和丟棄,浪費(fèi)資源。
graph TD
    A[查詢: skip(990).limit(10)] --> B[定位排序起點(diǎn)]
    B --> C[掃描前990條文檔]
    C --> D[丟棄這990條]
    D --> E[返回接下來的10條]
    style C fill:#f96,stroke:#333
    style D fill:#f96,stroke:#333

Java 中的skip()示例(不推薦用于深分頁)

public List<Document> getPostsByPage(int page, int pageSize) {
    int skip = (page - 1) * pageSize;
    List<Document> results = new ArrayList<>();
    posts.find()
         .sort(Sorts.descending("createdAt"))
         .skip(skip)
         .limit(pageSize)
         .into(results);
    return results;
}
// 調(diào)用
List<Document> page1 = getPostsByPage(1, 10); // 快
List<Document> page100 = getPostsByPage(100, 10); // 慢!

高效分頁方案:基于游標(biāo)的分頁(Cursor-based Pagination)

為了避免 skip() 的性能問題,推薦使用基于游標(biāo)的分頁,也稱為“鍵集分頁”(Keyset Pagination)。

核心思想 

不通過“跳過多少條”來分頁,而是通過“上一頁最后一條數(shù)據(jù)的排序鍵”來查詢下一頁。

例如:

  • 第一頁:sort(createdAt: -1).limit(10)
  • 第二頁:sort(createdAt: -1).limit(10).where(createdAt < 上一頁最后一條的createdAt)

為什么更高效?

  • 無需跳過數(shù)據(jù):直接從“斷點(diǎn)”處開始掃描。
  • 可充分利用索引:條件 createdAt < X 可以高效使用索引。
  • 性能穩(wěn)定:無論翻到第幾頁,查詢性能幾乎相同。

Java 實(shí)現(xiàn):基于時間戳的游標(biāo)分頁

import org.bson.conversions.Bson;
public class CursorPagination {
    private final MongoCollection<Document> posts;
    public CursorPagination(MongoCollection<Document> posts) {
        this.posts = posts;
    }
    /**
     * 獲取第一頁數(shù)據(jù)
     */
    public PagedResult getFirstPage(int pageSize) {
        List<Document> results = new ArrayList<>();
        Bson sort = Sorts.descending("createdAt");
        posts.find()
             .sort(sort)
             .limit(pageSize)
             .into(results);
        String cursor = results.isEmpty() ? null : 
                       results.get(results.size() - 1).getDate("createdAt").getTime() + "";
        return new PagedResult(results, cursor);
    }
    /**
     * 獲取下一頁數(shù)據(jù)
     */
    public PagedResult getNextPage(String cursor, int pageSize) {
        if (cursor == null || cursor.isEmpty()) {
            return new PagedResult(new ArrayList<>(), null);
        }
        long timestamp = Long.parseLong(cursor);
        Date cutoffDate = new Date(timestamp);
        List<Document> results = new ArrayList<>();
        Bson query = Filters.lt("createdAt", cutoffDate);
        Bson sort = Sorts.descending("createdAt");
        posts.find(query)
             .sort(sort)
             .limit(pageSize)
             .into(results);
        String nextCursor = results.isEmpty() ? null : 
                           results.get(results.size() - 1).getDate("createdAt").getTime() + "";
        return new PagedResult(results, nextCursor);
    }
}
// 輔助類
class PagedResult {
    private final List<Document> data;
    private final String nextCursor;
    public PagedResult(List<Document> data, String nextCursor) {
        this.data = data;
        this.nextCursor = nextCursor;
    }
    // Getters...
}

使用示例

CursorPagination pager = new CursorPagination(posts);
// 獲取第一頁
PagedResult page1 = pager.getFirstPage(10);
System.out.println("Page 1 has " + page1.getData().size() + " items");
// 獲取下一頁
PagedResult page2 = pager.getNextPage(page1.getNextCursor(), 10);
System.out.println("Page 2 has " + page2.getData().size() + " items");

處理重復(fù)值和復(fù)合排序

如果排序字段有重復(fù)值(如多個文檔在同一秒創(chuàng)建),簡單的 createdAt < T1 可能遺漏數(shù)據(jù)或重復(fù)返回。

解決方案:使用復(fù)合條件,包含唯一字段(如 _id)。

// 假設(shè) createdAt 有重復(fù),使用 createdAt 和 _id 聯(lián)合判斷
Document lastDoc = results.get(results.size() - 1);
Date lastDate = lastDoc.getDate("createdAt");
ObjectId lastId = lastDoc.getObjectId("_id");
Bson query = Filters.or(
    Filters.lt("createdAt", lastDate),
    Filters.and(
        Filters.eq("createdAt", lastDate),
        Filters.lt("_id", lastId)
    )
);

性能對比實(shí)驗(yàn)

讓我們通過一個實(shí)驗(yàn)直觀感受性能差異。

場景

  • 集合:posts,100萬條數(shù)據(jù)
  • 查詢:按 createdAt 降序,取第100頁(每頁10條)

方案1:skip()分頁

// 執(zhí)行時間:~850ms (示例值)
posts.find()
     .sort(Sorts.descending("createdAt"))
     .skip(990)
     .limit(10)
     .first();

方案2:游標(biāo)分頁

// 執(zhí)行時間:~15ms (示例值)
posts.find(Filters.lt("createdAt", cutoffDate))
     .sort(Sorts.descending("createdAt"))
     .limit(10)
     .first();

結(jié)果對比

graph Bar
    title 查詢性能對比 (ms)
    x-axis 方案
    y-axis 執(zhí)行時間 (ms)
    bar width 30
    "skip() 分頁" : 850
    "游標(biāo)分頁" : 15

游標(biāo)分頁比 skip() 分頁快了 50倍以上!

 結(jié)合索引:讓排序飛起來

無論使用哪種分頁方式,為排序字段創(chuàng)建索引都是性能優(yōu)化的前提。

創(chuàng)建排序索引

// 為 createdAt 字段創(chuàng)建降序索引
posts.createIndex(Indexes.descending("createdAt"));
// 復(fù)合排序索引
posts.createIndex(Indexes.compoundIndex(
    Indexes.ascending("category"),
    Indexes.descending("likes"),
    Indexes.descending("createdAt")
));

驗(yàn)證索引使用

使用 explain() 檢查執(zhí)行計劃:

Document explain = posts.find(Filters.lt("createdAt", new Date()))
                       .sort(Sorts.descending("createdAt"))
                       .limit(10)
                       .explain();
String stage = explain.get("executionStats", Document.class)
                     .get("executionStages", Document.class)
                     .getString("stage");
// 應(yīng)為 "IXSCAN" (索引掃描),而不是 "COLLSCAN" (集合掃描)

實(shí)際應(yīng)用:實(shí)現(xiàn)一個高效的博客文章API

讓我們整合所學(xué),實(shí)現(xiàn)一個生產(chǎn)級的分頁 API。

API 設(shè)計

  • GET /api/posts:獲取第一頁
  • GET /api/posts?after=cursor:獲取下一頁

Java Spring Boot 示例

@RestController
@RequestMapping("/api/posts")
public class PostController {
    @Autowired
    private MongoCollection<Document> posts;
    @GetMapping
    public ResponseEntity<PagedResponse> getPosts(
            @RequestParam(required = false) String after,
            @RequestParam(defaultValue = "10") int size) {
        List<Document> results;
        String nextCursor;
        if (after == null || after.isEmpty()) {
            // 第一頁
            results = getFirstPage(size);
            nextCursor = results.isEmpty() ? null : 
                        encodeCursor(results.get(results.size() - 1));
        } else {
            // 下一頁
            Date cutoffDate = decodeCursor(after);
            PagedResult page = getNextPage(cutoffDate, size);
            results = page.getData();
            nextCursor = page.getNextCursor() != null ? 
                        encodeCursor(results.get(results.size() - 1)) : null;
        }
        return ResponseEntity.ok(new PagedResponse(results, nextCursor));
    }
    private List<Document> getFirstPage(int size) {
        List<Document> list = new ArrayList<>();
        posts.find()
             .sort(Sorts.descending("createdAt"))
             .limit(size)
             .into(list);
        return list;
    }
    private PagedResult getNextPage(Date cutoffDate, int size) {
        List<Document> results = new ArrayList<>();
        Bson query = Filters.lt("createdAt", cutoffDate);
        posts.find(query)
             .sort(Sorts.descending("createdAt"))
             .limit(size)
             .into(results);
        String cursor = results.isEmpty() ? null : 
                       encodeCursor(results.get(results.size() - 1));
        return new PagedResult(results, cursor);
    }
    private String encodeCursor(Document doc) {
        // 將日期和_id編碼為安全的字符串
        long time = doc.getDate("createdAt").getTime();
        String id = doc.getObjectId("_id").toHexString();
        return Base64.getEncoder().encodeToString((time + ":" + id).getBytes());
    }
    private Date decodeCursor(String cursor) {
        byte[] decoded = Base64.getDecoder().decode(cursor);
        String[] parts = new String(decoded).split(":");
        long time = Long.parseLong(parts[0]);
        return new Date(time);
    }
}

響應(yīng)示例

{
  "data": [
    { "title": "Post 1", "createdAt": "2025-01-01T00:00:00Z" },
    { "title": "Post 2", "createdAt": "2025-01-02T00:00:00Z" }
  ],
  "next": "MTcyNzg0MDAwMDAwOmFiY2QxMjM0NTY="
}

監(jiān)控與調(diào)優(yōu)

使用 MongoDB Atlas 監(jiān)控

MongoDB Atlas 提供了直觀的性能監(jiān)控面板,可以實(shí)時查看查詢延遲、索引使用情況等。

啟用 Profiler

// 記錄慢查詢
db.setProfilingLevel(1, { slowms: 100 })

定期檢查 system.profile 集合,找出未使用索引的排序查詢。

查詢執(zhí)行計劃

// 在開發(fā)和測試中使用 explain
Document explain = collection.find(query)
                            .sort(sort)
                            .limit(10)
                            .explain(ExplainVerbosity.EXECUTION_STATS);
long executionTime = explain.get("executionStats", Document.class)
                           .getLong("executionTimeMillis");
long docsExamined = explain.get("executionStats", Document.class)
                          .getLong("totalDocsExamined");

推薦資源

 總結(jié)

sort()limit() 是 MongoDB 中實(shí)現(xiàn)有序數(shù)據(jù)展示的基石。但要真正發(fā)揮它們的威力,必須注意以下幾點(diǎn):

  1. 永遠(yuǎn)為排序字段創(chuàng)建索引,避免全表掃描。
  2. 避免在深分頁中使用 skip(),性能會隨著頁碼增加而急劇下降。
  3. 優(yōu)先采用基于游標(biāo)的分頁(Keyset Pagination),性能穩(wěn)定且高效。
  4. 處理好重復(fù)值,在復(fù)合排序中結(jié)合唯一字段(如 _id)確保準(zhǔn)確性。
  5. 使用 explain() 監(jiān)控,確保查詢計劃符合預(yù)期。

通過合理使用 sort()limit(),并采用正確的分頁策略,你的 MongoDB 應(yīng)用將能夠輕松應(yīng)對海量數(shù)據(jù)的有序展示需求,為用戶提供流暢的瀏覽體驗(yàn)?,F(xiàn)在,就去優(yōu)化你的分頁查詢吧!

到此這篇關(guān)于MongoDB排序與分頁之sort()和limit()的組合用法示例詳解(實(shí)踐指南)的文章就介紹到這了,更多相關(guān)mongodb sort()和limit()用法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • MongoDB插入數(shù)據(jù)的3種方法

    MongoDB插入數(shù)據(jù)的3種方法

    在MongoDB中,db.collection.insert()方法添加一個新文檔到集合中。另外,db.collection.update()方法和db.collection.save()方法也能通過upsert操作添加新文檔。upsert操作執(zhí)行更新現(xiàn)有文檔或者當(dāng)文檔不存在時插入一個新文檔。
    2014-05-05
  • MongoDB數(shù)據(jù)庫條件查詢技巧總結(jié)

    MongoDB數(shù)據(jù)庫條件查詢技巧總結(jié)

    查詢是數(shù)據(jù)庫的基本操作之一,下面這篇文章主要給大家介紹了關(guān)于MongoDB數(shù)據(jù)庫條件查詢技巧的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-06-06
  • centos離線安裝mongodb-database-tools方法詳解

    centos離線安裝mongodb-database-tools方法詳解

    這篇文章主要介紹了centos離線安裝mongodb-database-tools方法詳解的相關(guān)資料,需要的朋友可以參考下
    2022-11-11
  • 開發(fā)分布式醫(yī)療掛號系統(tǒng)MongoDB集成實(shí)現(xiàn)上傳醫(yī)院接口

    開發(fā)分布式醫(yī)療掛號系統(tǒng)MongoDB集成實(shí)現(xiàn)上傳醫(yī)院接口

    這篇文章主要介紹了開發(fā)分布式醫(yī)療掛號系統(tǒng)MongoDB集成實(shí)現(xiàn)上傳醫(yī)院接口,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-04-04
  • MongoDB數(shù)據(jù)庫與集合刪除操作方法詳解

    MongoDB數(shù)據(jù)庫與集合刪除操作方法詳解

    MongoDB是一款開源的文檔型數(shù)據(jù)庫管理系統(tǒng),屬于非關(guān)系型數(shù)據(jù)庫范疇,這篇文章主要介紹了MongoDB數(shù)據(jù)庫與集合刪除操作方法的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-10-10
  • MongoDB中的push操作詳解(將文檔插入到數(shù)組)

    MongoDB中的push操作詳解(將文檔插入到數(shù)組)

    $push操作符添加指定的值到數(shù)組中,下面這篇文章主要給大家介紹了關(guān)于MongoDB中push操作(將文檔插入到數(shù)組)的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-11-11
  • mongodb清除連接和日志的正確方法分享

    mongodb清除連接和日志的正確方法分享

    這篇文章主要給大家介紹了關(guān)于mongodb清除連接和日志的正確方法,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用mongodb具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2021-09-09
  • MongoDB利用oplog恢復(fù)數(shù)據(jù)的方法

    MongoDB利用oplog恢復(fù)數(shù)據(jù)的方法

    這篇文章主要介紹了MongoDB利用oplog恢復(fù)數(shù)據(jù)的方法,當(dāng)我們對數(shù)據(jù)出現(xiàn)誤操作的時候,可以利用oplog恢復(fù)數(shù)據(jù),下文操作過程需要的小伙伴可以參考一下
    2022-04-04
  • Mongodb億級數(shù)據(jù)性能測試和壓測

    Mongodb億級數(shù)據(jù)性能測試和壓測

    MongoDB是一個開源的、基于分布式文件存儲的NoSQL數(shù)據(jù)庫系統(tǒng),它使用文檔存儲方式,數(shù)據(jù)結(jié)構(gòu)由鍵值(key-value)對組成,本文給大家介紹了Mongodb億級數(shù)據(jù)性能測試和壓測,需要的朋友可以參考下
    2024-06-06
  • 基于MongoDB數(shù)據(jù)庫的數(shù)據(jù)類型和$type操作符詳解

    基于MongoDB數(shù)據(jù)庫的數(shù)據(jù)類型和$type操作符詳解

    下面小編就為大家?guī)硪黄贛ongoDB數(shù)據(jù)庫的數(shù)據(jù)類型和$type操作符詳解。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-07-07

最新評論

普安县| 都安| 汶川县| 长葛市| 闸北区| 巴林左旗| 井研县| 禄劝| 平遥县| 乌审旗| 泽州县| 抚顺市| 和平区| 澳门| 青州市| 湟源县| 壶关县| 沙湾县| 和顺县| 邛崃市| 巩义市| 彰化市| 英山县| 尤溪县| 舟曲县| 江津市| 雅安市| 本溪| 社旗县| 运城市| 益阳市| 全南县| 从江县| 尼勒克县| 松溪县| 富平县| 阳城县| 即墨市| 蓝山县| 都江堰市| 印江|