列表的動態排序和插入為常見的需求。然而,當面對大量資料時,傳統的「刪除重建」策略容易會成為效能瓶頸。本文將分享我在專案中如何透過 SeqNo 管理機制,將列表插入效能提升,同時保持資料一致性和系統穩定性。
在專案內有一下拉列表的功能,當使用者選擇某個選項後,會將該選項插入到列表中,並且會依據使用者的操作順序來進行排序。最初的實作方式是每次插入新選項時,都會重新計算整個列表的排序,這在資料量較大時,導致效能明顯下降。為了解決這個問題,引入了 SeqNo 管理機制。
原有問題
當使用者要在列表中間插入新的資料時,使系統面臨挑戰,並且在沒有加入 seqNo 管理機制前,容易遇到排序衝突的問題。
1 2 3 4 5 6 7
   |  [   { id: 'A', seqNo: 1 },   { id: 'B', seqNo: 2 },     { id: 'C', seqNo: 3 },   { id: 'D', seqNo: 4 } ]
 
  | 
 
如果直接將新項目設為 seqNo: 3,會與現有的項目 C 發生衝突。
傳統解決方案的困境
直觀的解決方案是重新排序所有項目:
1 2 3 4 5 6 7 8 9 10
   |    async function insertListData(afterId: string,    newListData: ListData) {          await deleteListDataAfter(afterId);
           await recreateListDataWithNewSeqNo(newListData,   subsequentListData);   }
 
  | 
 
- 操作複雜度: O(2N+1) - 其中 N 為列表總長度
 
- 資料庫壓力: 大量刪除和建立操作
 
- 交易風險: 操作步驟過多,失敗機率高
 
- 併發問題: 長時間鎖定,容易產生競爭條件
 
SeqNo 管理機制
 只更新真正需要變動的項目,將複雜度進行改善,是插入點之後的項目數量。
- 排序基礎函式
確保所有項目能依照 seqNo 正確排序。 
1 2 3 4 5 6 7 8
   |  export function sortListDataBySeqNo(listData: ListData[]): ListData[] {   return [...listData].sort((a, b) => {     const aSeqNo = a.seqNo || 0;     const bSeqNo = b.seqNo || 0;     return aSeqNo - bSeqNo;   }); }
 
  | 
 
- 插入策略計算
 
只計算「插入點之後」需要調整的項目,並產生更新操作清單。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
   |  export function calculateInsertStrategy(   existingListData: ListData[],   afterListDataId: string ): {   insertSeqNo: number;   affectedListData: ListData[];   updateOperations: Array<{ listDataId: string; newSeqNo: number }>; } {   const sortedListData = sortListDataBySeqNo(existingListData);   const afterIndex = sortedListData.findIndex(data => data.id === afterListDataId);
    if (afterIndex === -1) {     throw new Error('afterListDataId not found');   }
       const insertSeqNo = sortedListData[afterIndex].seqNo! + 1;      const affectedListData = sortedListData.slice(afterIndex + 1);
       const updateOperations = affectedListData.map(data => ({     listDataId: data.id,     newSeqNo: data.seqNo! + 1   }));
    return { insertSeqNo, affectedListData, updateOperations }; }
 
   | 
 
- 交易執行
確保所有更新操作能在同一個 Transaction 中完成。 
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | export async function executeSeqNoUpdates(   transaction: Transaction,   operations: Array<{ listDataId: string; newSeqNo: number }> ): Promise<void> {   for (const operation of operations) {          const listDataRef = adminDb.collection('listData').doc(operation.listDataId);     transaction.update(listDataRef, {       seqNo: operation.newSeqNo,       updatedAt: new Date()     });   } }
 
   | 
 
API 實現範例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
   |     export async function POST(req: Request) {   
    if (afterListDataId) {     try {              const { updateOperations, insertSeqNo } =         calculateInsertStrategy(existingListData, afterListDataId);
               const result = await adminDb.runTransaction(async (transaction) => {         await executeSeqNoUpdates(transaction, updateOperations);
                   const listDataRef = adminDb.collection('listData').doc();         transaction.set(listDataRef, {           ...newListData,           seqNo: insertSeqNo         });
          return { id: listDataRef.id, seqNo: insertSeqNo };       });
        return NextResponse.json(result, { status: 201 });     } catch (error) {       if (error.message === 'afterListDataId not found') {         return NextResponse.json({ message: 'afterListDataId not found' }, { status: 404 });       }       throw error;     }   } }
 
   | 
 
此專案的應用回顧與心得
這個問題最初是在「插入列表中間」時被發現的。
當時經常出現排序錯亂,追查後才發現是因為缺乏 SeqNo 管理機制,導致插入時 seqNo 發生衝突。
一開始的解法是 刪除重建,但這帶來兩個明顯的問題:
1.當資料量大時,對後端資料庫造成極大負擔。
2.使用者操作無法預測,若頻繁在列表中間插入或刪除,效能會快速下降。
引入 SeqNo 管理機制 後:
- 插入效能明顯提升
 
- 保持了資料一致性
 
- 系統穩定性也大幅改善
這樣的設計不僅解決了排序衝突,也大幅減少了資料庫的操作次數,讓整體效能更加穩定可