跳轉至

建立 2026-09-21 更新 2026-09-21

效能與執行限制

寫了很多 Google Apps Script,會不會拖慢彼此?單次能跑多久?這頁先回答第一個問題,再列出官方的執行限制與每日配額,最後給幾個實務上最有效的優化手法。

專案很多會有效能問題嗎

專案數量本身不是瓶頸。 每次執行都在 Google 雲端獨立跑,不佔你電腦的資源,也不會卡住試算表畫面。官方配額表裡也沒有「專案數量」這一項。真正會撞牆的是同一個帳號共用的總量

你擔心的 實際情況
專案多,整體會變慢 不會。執行彼此獨立;慢通常來自單支程式的寫法,不是專案數
一份表掛很多 onEdit 每次編輯都會啟動一次執行,不會卡住你打字,但每次都吃配額與並行數。多人同時編輯可能撞到並行上限
掛了很多時間觸發 每人每專案最多 20 個;更重要的是帳號所有觸發的總執行時間每天有上限(一般帳號 90 分鐘、Workspace 6 小時)
很多腳本一起寄信、打 API 寄信、UrlFetchApp 的每日次數是整個帳號共用,某支腳本跑失控會害其他腳本一起停擺
程式跑得慢 最常見原因是迴圈裡一格一格呼叫 SpreadsheetApp,每次呼叫都是一趟往返

還有一個實務觀察(官方文件沒寫數字):Web App 久沒人呼叫後的第一個請求會比較慢。需要即時回應的用途,例如聊天機器人,要預期偶爾有一次明顯的延遲。

執行限制

下表節錄自官方 配額與限制,數字會調整,上線前請再對一次。

項目 上限
單次執行時間 6 分鐘
簡易觸發、自訂函式、Workspace 外掛 30 秒
同時執行數 每位使用者 30、每支腳本 1,000
每人每專案的觸發條件數 20
UrlFetchApp 單次回應/POST 內容 各 50 MB
UrlFetchApp 網址長度 2 KB
屬性(Properties)單一值/總容量 9 KB/500 KB
每封郵件收件人數 50
每封郵件附件總大小 25 MB
專案版本數 200

超過時間會怎樣: 執行被直接終止,不會自動接著跑,也不會替你收拾半途的工作。長工作必須自己切段,見下文。

每日配額

項目 一般 Google 帳號 Google Workspace
寄出的收件人數 100 1,500
UrlFetchApp 呼叫次數 20,000 100,000
所有觸發的總執行時間 90 分鐘 6 小時
屬性讀寫次數 50,000 500,000
建立日曆事件 5,000 10,000
建立文件 250 1,500
建立試算表 250 3,200

配額用完之後,該功能會丟例外,通常隔天重置。監控最直接的方法:把重要腳本的失敗寫進 執行紀錄與錯誤工作表

怎麼讓腳本又快又穩

批次讀寫,不要逐格呼叫

// 慢:每一列都往返一次
for (let i = 2; i <= last; i++) {
  const v = sheet.getRange(i, 1).getValue();
  sheet.getRange(i, 2).setValue(v * 2);
}

// 快:讀一次、算完、寫一次
const values = sheet.getRange(2, 1, last - 1, 1).getValues();
const out = values.map(([v]) => [v * 2]);
sheet.getRange(2, 2, out.length, 1).setValues(out);

用快取避免重複打 API

CacheService 是暫存區:值最大 100 KB、最多 1,000 項、預設保留 10 分鐘、最長 6 小時。適合存「不常變、又常被查」的結果,例如匯率、設定表。

function getRate() {
  const cache = CacheService.getScriptCache();
  const hit = cache.get("rate");
  if (hit) return Number(hit);
  const res = UrlFetchApp.fetch("https://api.example.com/rate");
  const rate = JSON.parse(res.getContentText()).rate;
  cache.put("rate", String(rate), 600); // 存 10 分鐘
  return rate;
}

多個請求用 fetchAll 一次送

UrlFetchApp.fetchAll(requests) 讓多個請求並行送出,比在迴圈裡逐一 fetch 快很多,也較不容易撞到 6 分鐘。

同時寫入用 LockService 排隊

多人同時觸發同一支腳本(例如很多人同時編輯、機器人同時收到訊息),若做的是「先讀再改同一格」,會互相覆蓋。用鎖讓它們一個一個做:

function safeIncrement() {
  const lock = LockService.getScriptLock();
  lock.waitLock(10000); // 最多等 10 秒,逾時會丟例外
  try {
    const cell = SpreadsheetApp.getActive().getRange("Counter!A1");
    cell.setValue(Number(cell.getValue()) + 1);
  } finally {
    lock.releaseLock();
  }
}

長工作切段續跑

大表逐列處理可能超過 6 分鐘。做法:跑到接近上限就把進度存進 PropertiesService,排一個一分鐘後的一次性觸發接著跑。

const MAX_MS = 4.5 * 60 * 1000; // 留餘裕,別跑滿 6 分鐘

function processAll() {
  const start = Date.now();
  const props = PropertiesService.getScriptProperties();
  const sheet = SpreadsheetApp.getActive().getSheetByName("待處理");
  const last = sheet.getLastRow();
  let row = Number(props.getProperty("nextRow") || 2);

  clearResumeTriggers();
  while (row <= last) {
    if (Date.now() - start > MAX_MS) {
      props.setProperty("nextRow", String(row));
      ScriptApp.newTrigger("resumeJob").timeBased().after(60 * 1000).create();
      return;
    }
    handleRow(sheet, row); // 你自己的單列處理
    row++;
  }
  props.deleteProperty("nextRow"); // 全部做完,清掉進度
}

function resumeJob() {
  processAll();
}

function clearResumeTriggers() {
  ScriptApp.getProjectTriggers()
    .filter((t) => t.getHandlerFunction() === "resumeJob")
    .forEach((t) => ScriptApp.deleteTrigger(t));
}

先清掉舊的接力觸發再建新的,才不會像 觸發條件 提醒的那樣疊出一堆。

簡易觸發要早退

onEdit 每次編輯都會跑。第一行就判斷工作表、欄位,不相關就 return。這是零成本、效果最大的優化。

什麼時候該離開 GAS

需求 為什麼 GAS 吃力 常見替代
高流量、要穩定低延遲 有冷啟動、有並行與每日配額 Google Cloud 的 Cloud Run 等
單次運算超過 6 分鐘 會被強制終止 排程的雲端工作、佇列
需要真正的資料庫、交易 試算表不是資料庫 Cloud SQL、Firestore 等
要讀 HTTP 標頭(如驗證 Webhook 簽章) Web App 拿不到請求標頭 前面放一層中繼,見 LINE Bot 上線注意

多數辦公室自動化離這些門檻很遠。先寫,撞到再換不遲。