MongoDB 如何實現實時排名

2021-09-19 10:58:26 字數 1924 閱讀 4253

當我們將考試分數錄入系統時,會要對學生的分數進行乙個排名,這個不困難。困難的是當學生的分數變更時,如何實時更新這些排名?

如果我們將排名儲存為乙個字段,那麼意味著每次修改分數都會導致重新計算排名,以及更新資料庫中的排名字段值。這個計算量可大可小,極端的情況下,如果乙個學生的分數從第一名變成 0(比如因為作弊而成績清零),那麼所有學生的排名都有可能要改,這就導致大批量的資料庫 update 操作。

這種方式顯然是效率非常低的。那麼這裡給出乙個解決辦法,能夠實現修改分數的同時,排名立刻得到更新,而無需大規模修改資料庫記錄。本文以 mongodb 為例介紹如何儲存這些分數。

假設我們有 10 個學生的考試成績分別為:

50 50 50 60 60 60 60 80 90 100
那麼我可以建立一條排名記錄(假設這條記錄在集合scores裡面),內容如下:

,,,

,]}

看到這裡你就應該明白了,我儲存的只是每個分數對應的人數。這樣當我需要新增乙個 80 分的成績時,我只需將改為即可。

這種情況下如何獲得乙個學生的排名呢?首先要說明,每個學生對應的分數並沒有儲存在這裡(你可以儲存到另外的表裡面,然後查出該學生的分數),這裡儲存的是分數的排名,而不是學生的排名。所以你查出分數後,對scoremap陣列中所有 score 大於該分數的元素的count值進行乙個總和即可。

比如我想知道 85 分是第幾名,從陣列中可以算知大於 85 分的人數為 2,那麼 85 分自然是第 3 名了。

那麼在 mongodb 裡面,可以這樣查詢:

db.scores.aggregate([},,

}},}}

]);

查出來的結果為:

這種儲存方式有乙個極大的好處,就是不論有多少學生參加排名,我的記錄數始終是有限的。假設這門考試總分 100 分,有 100 萬學生參加排名,那麼極端情況下scoremap陣列中也只會有 201 個元素(0, 0.5, 1, ... 99.5, 100)。也就是說,它的值空間是非常有限的

有人會進一步提出:如果我將班級平均分(帶兩位小數)也加入這樣的排名呢?值空間將會變成幾萬個,這樣的排名查詢起來豈不會很慢?

實際上不是這樣子的。雖然兩位小數使得值空間很大,但參與排名的班級非常有限,也就是說值的實際數量很小。如果是 100 個班級參與排名,不存在相同平均分的情況下,scoremap陣列中也只有 100 個元素。所以計算排名絲毫不會慢下來。

現在我們的排名已經不再是直接儲存在資料庫,而是在查詢的時候算出來,那麼當乙個學生的成績變更時,我應該如何更新資料庫記錄呢?

當乙個學生的成績從 a 變為 b 時,我們需要做兩個操作:

將成績為 a 的學生數量 -1;

將成績為 b 的學生數量 +1。

在 mongodb 中,更新scoremap陣列元素的語句是這樣的:

給某個分數的人數加一的時候:

db.scores.update(

}},}

)

給某個分數的人數減一的時候:

db.scores.update(

}}},

})

注意減一的時候,查詢條件加上了乙個count:的判斷,這是為了避免人數變成負數。

總之,通過這樣的儲存方式,我們能夠實現當學生成績改變時,快速簡單的修改資料庫記錄,而又能夠實時的查詢出新的排名了。

redis排重 使用Redis實現實時排名

redis用途很廣泛,分布式使用者session快取 爬蟲url佇列 活動頁面的動態列表資訊等。使用redis實現排行榜系統也是很常見的方案。假如設計乙個積分排名系統。如果積分資料都存放在資料庫中,積分的更新是動態的,每次訪問排行頁面都需要對資料進行重新排序,在真實的產品應用中幾乎是不可接受的。re...

JS如何實現實時獲取網路時間

首先我們列出常用的js獲取網路時間的 getfullyear 獲取年份 getmonth 獲取月份 0 11 getdate 獲取日 gethours 獲取小時 getminutes 獲取分鐘 getsconds 獲取秒 不會自動更新 注意 月份獲取 預設月份從0 11,所以我們使用時為達到正常使用...

PHP實現實時輸出

這個問題是在本地的環境中發現的。指令碼輸出的內容要等全部執行完畢後才會輸出,而sae是實時輸出的,避免了長時間等待。網上摘錄,純筆記。header content type text html charset utf 8 設定執行時間不限時 set time limit 0 清除並關閉緩衝,輸出到瀏...