大量資料如何做分頁處理

华为云开发者联盟發表於2024-04-10

本文分享自華為雲社群《應用中大量資料的分頁處理》,作者:碼樂。

簡介

大批次資料的展示一直被視為一個必須要解決的問題。 一個經典的思想就是分批展示和處理它們。

1 查詢時外來鍵的處理

如果在django model 中模型使用了外來鍵,透過on_delete 來定義關聯操作。

CASCADE: 級聯操作。如果外來鍵那條資料刪除了,這條資料也將被刪除
PROTECT: 受保護,只要這條資料引用了外來鍵的那條資料,舊不能刪除外來鍵資料,如果強行刪除,Django框架將報錯
SET_NULL: 設定為空,如果外來鍵資料被刪除,本條資料設定為空,前提是 可以設定本條資料為空
SET_DEFAULT: 設定預設值,如果外來鍵資料刪除了,設定這個資料的值為預設,前提是有預設值
SET()函式: 如果外來鍵那條資料被刪除,那麼將會獲取SET函式的值作為外來鍵的值。Set()函式可以接受可呼叫物件,可呼叫物件的返回值作為結果設定回去。
DO_NOTHING: 不採取任何行為,一切看資料庫級別的行為。

資料庫層面的約束:

    PESTRICT: 預設選項,如果要刪除父表記錄,如果子表有關聯記錄,則不允許刪除
    NOACTION:同上,首先檢測外來鍵
    CASCADE: 父表delete,update時,子表關聯操作 也進行 delete,update
    SET NULL:父表delete , update時,子表將關聯記錄外來鍵欄位設定為null,所以設計子表時不能 not null

這些外來鍵的方法工具,可以幫助使用者處理多表關聯查詢任務。

1.1 如何在django中查詢分頁

在有分頁查詢的應用中,包括 LIMIT 和 OFFSET 的查詢十分常見,而且幾乎每個都會有一個 ORDER BY 子句。

如果使用索引排序的話將對效能最佳化十分有幫助,否則服務端需要做很多檔案排序。

一個高頻的問題是 offset 的值過大。如果查詢類似 LIMIT 10000, 20,將會產生10020行,並將之前的10000行丟棄,這樣的代價很高。

    select * from table order by id limit 10000, 20;

很簡單,該語句的意思就是查詢10000+20條記錄,去掉前10000條,返回後20條。
無疑該查詢能夠實現分頁,但10000這位置的值越大,查詢效能就越低,因為MySQL需要掃描全部10000+20條記錄。

假設所有的頁使用相同的頻次訪問,這樣的查詢將平均掃描一半資料表。為了最佳化他們,你可以在分頁檢視中限制最多可訪問的頁數,或者讓大批次的查詢更有效。

當一個表中有很多符合查詢條件的資料的時候,我們往往不需要把他們全部一次性取出來,那樣對查詢效率或者伺服器效能來說都會是一個極大的挑戰:例如最簡單的商城,假設商城中有一萬個資料,但我們在前端可能只會每次看到一頁.

    select * from table where xxx="xxx" limit 10; 

這表示查詢符合條件的10個資料。

    select * from table where xxx="xxx" limit 10 offset 10;

這表示分頁,查詢符合條件的第11到20的資料。

或者透過指定最大id去查詢

    select * from table where id > #max_id# order by id limit n;

該查詢同樣會返回後n條記錄,卻無需像方式1掃描前m條記錄,但必須在每次查詢時拿到上一次查詢(上一頁)的最大id(或最小id),是比較常用的方式。

當然該查詢的問題也在於,如果最大id不是連續的,則我們不一定能拿到這個id,比如當前在第3頁,需要查詢第5頁的資料,就不行了。

或者透過子查詢,先篩選前10000個,找到最大id,然後選擇剩餘的20個符合要求的

    select * from table where id > (select id from table order by id limit m, 1) limit n;

該查詢同樣是透過子查詢掃描欄位id, 因為它不需要進行表的關聯,而是一個簡單的比較,在不知道上一頁最大id的情況下,是比較推薦的用法。

左右連線的方式本身效能可能更差。
還有如下子查詢、連線表,加索引快速定位元組,然後再讀取元組

    SELECT * FROM table WHERE id <= (SELECT id FROM table ORDER BY id DESC LIMIT (page-1)*pagesize ORDER BY id DESC LIMIT pagesize)

rest_framework 內建了分頁的操作模組,讓我們來應用到具體函式即可 employee/views.py

from rest_framework.pagination import PageNumberPagination
@api_view(['GET', 'POST']) 
@permission_classes([CustomPermission])
def blog_api_view(request):
    """"""
    if request.method == "GET":
        paginator = PageNumberPagination()
        # paginator.page_size = 1 setting we display only 1 item per page.
        paginator.page_size = 2
        task_objects = EmployeeSign.objects.all()
        result = paginator.paginate_queryset(task_objects, request)

如果不使用分頁,將顯示全部的訊息在同一個頁面

        serializer = TaskSerializer(result, many=True)
        return Response(serializer.data)

訪問分頁資料.預設介面http://127.0.0.1:2001/api/tasks/ 就是分頁1

http://127.0.0.1:2001/api/tasks/?page=1  #2,3,4...

2 小結

再重複一次,在有分頁查詢的應用中,包括 LIMIT 和 OFFSET 的查詢十分常見,而且幾乎每個都會有一個 ORDER BY 子句。如果使用索引排序的話將對效能最佳化十分有幫助,否則服務端需要做很多檔案排序。

一個高頻的問題是 offset 的值過大。如果查詢類似 LIMIT 10000, 20,將會產生10020行,並將之前的10000行丟棄,這樣的代價很高。

假設所有的頁使用相同的頻次訪問,這樣的查詢將平均掃描一半資料表。

為了最佳化他們,你可以在分頁檢視中限制最多可訪問的頁數,或者讓大量的查詢更有效。

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章