多用户并发调用Apps Script库时如何用Utilities.getUuid()避免行覆盖
解决方案
问题根因
你之前用的Apps Script原生LockService无效的核心原因是:公共库被不同用户的独立表格调用时,每个调用方的脚本实例是独立的,LockService生成的锁仅作用于当前调用方的实例,无法跨调用方共享,自然没法阻止并发冲突。
方案1:基于UUID实现跨实例共享乐观锁(最贴合你现有架构)
利用你提到的Utilities.getUuid(),结合Master Tracker表格内置的隐藏工作表作为共享锁载体,实现全局唯一的锁控制,所有读写Master Tracker的操作必须持有锁才能执行:
操作步骤
- 打开你的Master Tracker表格,新建一个名为
_Lock的隐藏工作表,A1存当前持有锁的UUID,B1存锁的过期时间戳,初始状态A1/B1留空。 - 在你的公共库中新增抢锁、释放锁的公共方法:
// 抢锁,成功返回true,失败返回false function acquireLock() { const lockSheet = SpreadsheetApp.openById('你的Master Tracker表格ID').getSheetByName('_Lock'); const myUuid = Utilities.getUuid(); const maxRetry = 30; // 最多重试30次,每次等1秒,总超时30秒 const lockExpireMs = 5 * 60 * 1000; // 锁最多持有5分钟,避免异常崩溃导致锁永久占用 for (let i = 0; i < maxRetry; i++) { const curLock = lockSheet.getRange(1,1,1,2).getValues()[0]; const curUuid = curLock[0]; const curExpire = curLock[1] || 0; const now = Date.now(); // 锁已过期或者为空,可以抢 if (!curUuid || now > curExpire) { lockSheet.getRange(1,1,1,2).setValues([[myUuid, now + lockExpireMs]]); SpreadsheetApp.flush(); // 二次确认抢锁成功,避免同时抢的冲突 const confirmLock = lockSheet.getRange(1,1).getValue(); if (confirmLock === myUuid) return true; } Utilities.sleep(1000); } return false; } // 释放锁 function releaseLock() { const lockSheet = SpreadsheetApp.openById('你的Master Tracker表格ID').getSheetByName('_Lock'); lockSheet.getRange(1,1,1,2).clearContent(); SpreadsheetApp.flush(); }
- 重构你原来的读写逻辑,所有操作必须放在抢锁成功后的逻辑块里,执行完必须释放锁:
// 先抢锁 const hasLock = acquireLock(); if (!hasLock) throw new Error('操作繁忙,请稍后重试'); try { const masterSheet = SpreadsheetApp.openById('你的Master Tracker表格ID').getSheetByName('Master Tracker'); // 一次性读全表数据,不要循环逐行读,效率高还避免中间状态变化 const allData = masterSheet.getDataRange().getDisplayValues(); let targetRow = -1; // 找匹配date和name的行 for (let i = 0; i < allData.length; i++) { if (allData[i][0] === date && allData[i][1] === name) { targetRow = i + 1; // 数组下标转表格行号 break; } } if (targetRow === -1) { // 没找到就追加行 masterSheet.appendRow([date, name]); targetRow = masterSheet.getLastRow(); } // 后续写对应列的逻辑直接用targetRow就行,不需要sleep也不需要反复校验 // 这里放你原来的写列逻辑,替换掉原来的循环校验部分 for (var i = 1; i < data.length; ++i) { var row = data[i]; var cat_name = row[0]; var cat_count = row[1]; var colNum = getColByName(cat_name); if (colNum) masterSheet.getRange(targetRow, colNum).setValue(cat_count); } SpreadsheetApp.flush(); } finally { // 不管操作成功失败,必须释放锁 releaseLock(); }
注意事项
- 所有对Master Tracker的读写操作必须放在锁的保护范围内,不要漏
- 锁的超时时间和重试次数可以根据你的实际并发量调整
方案2:Web App中转写入(更稳定,适合高并发场景)
如果你的并发量很高,自己实现锁容易出问题,可以把写Master Tracker的逻辑单独封装为一个Web App:
- 新建一个独立的Apps Script项目,写好接收参数、写入Master Tracker的逻辑,内部用原生
LockService做并发控制即可,因为所有请求都进入同一个脚本实例,锁是全局生效的 - 部署Web App,运行身份选择你自己(库开发者),访问权限设置为任何人
- 你的公共库不再直接操作Master Tracker,而是把要写入的数据POST到这个Web App的地址,由Web App统一完成写入操作
内容的提问来源于stack exchange,提问作者Dome Photography
相关产品推荐
相关产品推荐

