記一次線上數據庫刪除百萬級數據

背景

不知道有沒有讀者經歷過刪除線上數據庫記錄,反正這事對我來說是挺有壓力的。動地心,顫抖的手,萬一刪錯了數據就麻煩了,影響數量大,領導會批評,還有覆盤自己批評,想想就頭大。那麼就要在刪除數據之前做好計劃,避免刪數據時發生意料之外的事情。記錄一次刪除線上數據。

業務以半年爲一個週期,每一個週期體驗參與用戶在40w左右。新一期開始後過期體驗用戶的數據較大,會讓數據庫增刪改查速度變慢,但這一部分數據對業務已經沒有幫助了,所以要清理過期體驗用戶的數據。

一共要清理5張表:
A:數量 36w
B:數量 36w
C:數量 140w
D:數量 36w
E:數量 72w

要求

對於大量數據的查詢和刪除不能使用一條語句完成,會造成數據庫阻塞,特別是刪除數據時會鎖表或鎖行。要減少對線上正常業務的影響,在刪除數據的同時不能影響數據庫正常增刪改查,而且不能讓數據庫查詢等待過長而導致API返回超時。所以查詢和刪除都需要分批完成。

刪除數據底層流程

簡單的說明一下在刪除數據時MySQL數據底層流程。
MySQL數據庫分爲兩個部分:server層和存儲引擎,如下圖:

在刪除一條記錄時,流程分別是:

  1. 客戶端連接到server層
  2. 分析器對sql語句進行詞法分析和語法分析,知道是一條刪除語句
  3. 優化器選擇合適的索引去查找待刪除的記錄
  4. 執行器調用存儲引擎刪除所有記錄
  5. 存儲引擎將維護的數據內存裏的記錄標記爲已刪除
  6. 存儲引擎根據刷盤規則將內存的記錄寫到磁盤中

以上就是刪除一條語句的過程,因爲每一個sql語句是一個事務,每一個事務都涉及到加鎖,所以在以上的刪除流程中,鎖的使用是這樣的:

  1. 如果查詢待刪除記錄走的是索引,那麼會將所有刪除記錄行加鎖
  2. 如果查詢待刪除記錄沒有走索引,那麼將會將整張表都鎖起來,直到刪除完成

刪除實施

針對最多140w的數據,肯定不能一次性刪除,否則鎖表的時間會讓其他業務無法進行,同時要在刪除時使用行鎖而不是表鎖。所以刪除分爲兩步走:

  1. 使用分塊查詢,將所有記錄的主鍵查詢出來
  2. 根據主鍵分批刪除,同時每一次刪除間隔一定時間,異常捕獲不影響刪除流程

經過5張表的刪除,得出以下腳本刪除時對數據庫影響較小

# 分批查詢,只查詢id
def chunk_model(model, conditions, start_id=0, chunk_size=500):
    while True:
        models = list(
            model.select(model.id)
            .where(model.id > start_id, *conditions)
            .order_by(model.id.asc())
            .limit(chunk_size)
        )
        if not models:
            break

        yield models
        start_id = models[-1].id


import time
from app.camp.models import UserPlan

plan_id_list = [33,34]

delete_id_list = []
for chunk_data in chunk_model(UserPlan, [UserPlan.plan_id.in_(plan_id_list)]):
    print(chunk_data[0].id)
    for data in chunk_data:
        delete_id_list.append(data.id)

# 分批刪除
for id_index in range(0, len(delete_id_list), 1000):
    print(id_index)
    try:
        delete_ids = delete_id_list[id_index:id_index+1000]
        UserPlan.delete().where(UserPlan.id.in_(delete_ids)).execute()
        time.sleep(0.5)
    except Exception as e:
        print(id_index, e)

最多的140w數據耗時在10分鐘左右,全程數據庫無波動,業務沒有影響。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章