Khắc phục lỗi 'Lock timeout: Could not obtain lock after 30 seconds' trong Google Apps Script

intermediate📗 Google Sheets2026-07-15| Google Apps Script (GAS), trình kích hoạt Google Sheets (onFormSubmit, onEdit, trình kích hoạt theo thời gian).

Error Message

Lock timeout: Could not obtain lock after 30 seconds.
#google-apps-script#lockservice#tối-ưu-hóa#google-sheets#đồng-thời

Tình huống lỗiHãy tưởng tượng Google Sheet của bạn là backend cho một biểu mẫu đăng ký quan trọng. Trong các bài kiểm tra cá nhân, mọi thứ chạy hoàn hảo. Nhưng ngay khi triển khai thực tế và có hơn 50 người dùng nhấn 'Gửi' trong cùng một phút, nhật ký thực thi của bạn sẽ chuyển sang màu đỏ. Bạn gặp phải rào cản: Lock timeout: Could not obtain lock after 30 seconds.

Điều này thường xảy ra do nhiều phiên bản của tập lệnh đang tranh chấp cùng một tài nguyên. Khi một phiên bản đang giữ khóa (lock), những phiên bản khác phải xếp hàng chờ. Nếu hàng chờ đó không di chuyển đủ nhanh, Google sẽ dừng tiến trình.

Tại sao điều này xảy raGoogle Apps Script sử dụng LockService để ngăn chặn tình trạng race condition, chẳng hạn như hai người dùng cùng ghi đè vào một ô cùng lúc. Khi bạn gọi lock.waitLock(30000), bạn đang thiết lập bộ hẹn giờ 30 giây. Nếu tập lệnh hiện đang giữ khóa không hoàn thành và giải phóng nó trong khoảng thời gian đó, tập lệnh của bạn sẽ ném ra một ngoại lệ và dừng lại.

Nguyên nhân gốc rễ thường là do 'critical section' — đoạn mã giữa lúc lấy và giải phóng khóa — quá cồng kềnh. Nếu tập lệnh của bạn mất 5 giây để chạy khi đang giữ khóa, bạn chỉ có thể xử lý 6 người dùng đồng thời trước khi người dùng thứ 7 gặp lỗi timeout 30 giây.

Cách khắc phục nhanh: Xử lý lỗi khéo léoĐừng để tập lệnh của bạn bị sập và để lại cho người dùng một thông báo lỗi chung chung. Hãy bao bọc nỗ lực lấy khóa của bạn trong một khối try-catch. Điều này cho phép bạn ghi lại nhật ký lỗi hoặc thông báo cho người dùng thay vì để quá trình thực thi thất bại trong âm thầm.

function processData() {
  var lock = LockService.getScriptLock();
  
  try {
    // Cố gắng lấy khóa trong 30 giây
    lock.waitLock(30000);
  } catch (e) {
    console.warn('Timeout: Không thể lấy khóa. Máy chủ có thể đang quá tải.');
    // Tùy chọn: Gửi cảnh báo qua email nếu điều này xảy ra thường xuyên
    return;
  }

  // --- BẮT ĐẦU CRITICAL SECTION ---
  // Giữ khu vực này tinh gọn nhất có thể
  SpreadsheetApp.flush(); 
  // --- KẾT THÚC CRITICAL SECTION ---

  lock.releaseLock();
}

Cách khắc phục lâu dài: Thu hẹp 'Vùng khóa'Bí quyết để có các tập lệnh hiệu suất cao không phải là chờ đợi lâu hơn; mà là khóa trong thời gian ngắn hơn. Bạn muốn tập lệnh của mình giữ khóa trong vài mili giây chứ không phải vài giây.

1. Di chuyển phần chuẩn bị dữ liệu ra ngoài khóaĐừng bao giờ giữ khóa khi đang thực hiện các tính toán nặng hoặc lấy dữ liệu từ các API bên ngoài. Hãy chuẩn bị sẵn mọi biến bạn cần trước, sau đó chỉ mở khóa cho thao tác ghi cuối cùng.

// NÊN TRÁNH: Khóa trong khi gọi API mất 10 giây
lock.waitLock(30000);
var response = UrlFetchApp.fetch(url); // Chậm!
sheet.appendRow([response]);
lock.releaseLock();

// TỐT HƠN: Lấy dữ liệu trước, sau đó khóa trong 0,2 giây
var response = UrlFetchApp.fetch(url); 
var data = response.getContentText();

lock.waitLock(30000);
sheet.appendRow([data]); 
SpreadsheetApp.flush(); // Ép buộc ghi dữ liệu ngay lập tức
lock.releaseLock();

2. Sử dụng SpreadsheetApp.flush()Google Sheets thường gộp các thay đổi để tiết kiệm năng lượng. Tuy nhiên, bên trong một khóa, bạn cần các thay đổi đó được cam kết vào cơ sở dữ liệu ngay lập tức. Gọi SpreadsheetApp.flush() đảm bảo dữ liệu được ghi trước khi khóa được giải phóng. Điều này ngăn tập lệnh tiếp theo trong hàng chờ đọc phải dữ liệu 'cũ'.

3. Triển khai Exponential BackoffNếu tập lệnh của bạn xử lý các đợt lưu lượng truy cập lớn đột ngột, một lần chờ 30 giây có thể là không đủ. Bạn có thể triển khai một vòng lặp thử lại với độ trễ ngẫu nhiên nhỏ. Điều này ngăn chặn vấn đề 'thundering herd' khi nhiều tập lệnh cố gắng lấy khóa tại cùng một mili giây.

function robustLock() {
  var lock = LockService.getScriptLock();
  var success = false;
  var attempts = 0;

  while (!success && attempts < 3) {
    try {
      lock.waitLock(15000); // Thử trong 15 giây
      success = true;
    } catch (e) {
      attempts++;
      // Chờ từ 500ms đến 1500ms trước khi thử lại
      Utilities.sleep(Math.floor(Math.random() * 1000) + 500); 
    }
  }
  
  if (!success) throw "Hệ thống bận. Vui lòng thử lại sau giây lát.";
  
  // ... thực hiện các thao tác ghi ...
  
  lock.releaseLock();
}

Xác minh cách khắc phụcKiểm tra tab Executions (Thực thi) trong bảng điều khiển Apps Script của bạn để xem các thay đổi có hoạt động hay không. Hãy tìm ba chỉ số sau:

  • Trạng thái: Bạn sẽ thấy 'Completed' (Hoàn thành) thay vì 'Timed out' (Hết thời gian).- Thời lượng: Nếu các lần thực thi liên tục chạm mốc 30 giây, khóa của bạn vẫn là một nút thắt cổ chai.- Độ đồng thời: Sử dụng console.log để theo dõi xem cơ chế exponential backoff của bạn thực tế đang thực hiện bao nhiêu lần thử lại.Nếu lỗi vẫn tiếp diễn, hãy đánh giá xem bạn có cần getScriptLock() (chặn tất cả mọi người) hay chỉ cần getDocumentLock() (chỉ chặn người dùng của tệp cụ thể đó) là đủ cho trường hợp của bạn.

Related Error Notes