Google 应用程序脚本 360 秒超出时间限制解决方法

Posted

技术标签:

【中文标题】Google 应用程序脚本 360 秒超出时间限制解决方法【英文标题】:Google apps script 360 seconds exceeded time limit work around 【发布时间】:2017-03-23 18:23:21 【问题描述】:

将 google 表格数据导入 mysql 表并尝试解决 360 秒执行限制的编码。我正在使用批处理执行,即使这也无助于导入具有更大数据的工作表。谷歌应用程序脚本中有没有一种方法可以在 350 秒或记录数(以先到者为准)时中断,并让下一次执行从中断的地方继续执行。

var address = 'database_IP_address';
var rootPwd = 'root_password';
var user = 'user_name';
var userPwd = 'user_password';
var db = 'database_name';

var root = 'root';
var instanceUrl = 'jdbc:mysql://' + address;
var dbUrl = instanceUrl + '/' + db;

function googleSheetsToMySQL()    

  var RecId;
  var Code;
  var ProductDescription;
  var Price;

  var dbconnection = Jdbc.getConnection(dbUrl, root, rootPwd);
  var statement = dbconnection.createStatement();
  var googlesheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('product'); 
  var data = googlesheet.getDataRange().getValues();  

dbconnection.setAutoCommit(false)

for (var i = 1; i < data.length; i++) 
RecId = data[i][0];
Code = data[i][1];
ProductDescription = data[i][2];
Price = data[i][3];

var sql = "call [dbo].[sp_googlesheetstotable](?,?,?,?)";
statement = dbconnection.prepareCall(sql);
statement.setString(1, RecId);
statement.setString(2, Code);
statement.setString(3, ProductDescription);
statement.setString(4, Price);
statement.addBatch()
statement.executeBatch()


dbconnection.commit()

【问题讨论】:

【参考方案1】:

在这种情况下,我要做的是创建一个空工作表,允许我存储数据库的起始 ID,然后在脚本上设置一个触发器,使其每 10 分钟运行一次。为此,只需单击 GAS 编辑器中播放按钮左侧的小时钟图标。

例如,假设我有一个包含 1000 条记录的数据库,并且每次触发器运行脚本时我想提升 100 条记录。我会创建一个工作表 - 比如说“Sheet1” - 最初将 0 放在单元格 A1 中。

在 GAS 中,我会让脚本在每次运行时获取 A1 的值:

var ss = SpreadsheetApp.openById("XXXXX");
var sheet = ss.getSheetByName("Sheet1");
var a1 = sheet.getRange("A1").getValue();
var a1 = parseInt(a1);
var last_row = a1+100;

那么您的 SQL 语句应该类似于以下查询:

"SELECT * FROM `table` WHERE `id` >= '"+a1+"' AND `id` < '"+last_row+"'"

一旦查询完成并将其结果输出到电子表格,您就可以将A1 更新为last_row

sheet.getRange("A1").setValue(last_row);

当脚本再次运行时(通过触发器),它将采用新值100,添加100(使其变为200),查询表中大于或等于100的结果和小于 200,并相应显示。

【讨论】:

【参考方案2】:

是的,只要问题是由循环过程引起的,这很容易实现。如果没有循环,则有点棘手,如果是单个操作,则不可能。我们很幸运,你的似乎是第一种。

本质上我们首先需要知道脚本什么时候开始,所以我们做一个

var startTime = new Date();

获取开始时间。接下来我们需要检查每次迭代后的时间,因此在您的for 循环中,我们将添加(我个人会先声明变量并且此处没有 var,但这只是我的风格偏好)

var execTime = new Date();
execTime = execTime - startTime

告诉我们脚本执行了多长时间。如果你想变得花哨,你可以检查你的循环平均需要多长时间并检查是否还有足够的时间,但通常只给它一些时间来执行截止就足够了。然后你需要做一些事情。首先,您希望通过使用来存储您拥有的数据

PropertiesService.getScriptProperties().setProperty(key, value)

其中key 是属性名称,value 是您要存储的对象。如果它是一个数组,请确保使用JSON 对其进行字符串化。您还需要创建一个触发器以从您离开的地方触发。阅读更多关于 here 的信息。你当然可以先创建触发器,然后存储属性,没有问题。

这是在我的脚本中执行此段的函数示例:

function autoTrigger(passProperties, sysKeys)   
  var sysProperties = new systemProperties();

  if (typeof sysKeys === 'undefined' || sysKeys === null) 
    sysKeys = new systemKeys();
  

  var triggerID = ScriptApp.newTrigger('stateRebuild')
                           .timeBased()
                           .after(60000)
                           .create()
                           .getUniqueId();

  Logger.log('~~~ RESTART TRIGGER CREATED ~~~');

//-------------------------------------------------------------------------------------------------
// In order to properly retrieve the time later, it is stored in milliseconds
  passProperties.timeframe.start = passProperties.timeframe.start.getTime();
  passProperties.timeframe.end = passProperties.timeframe.end.getTime();
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
// Properties are stored in User Properties using JSON
  PropertiesService.getUserProperties()
                   .setProperty(sysKeys.startup.rebuildCache, JSON.stringify(passProperties));
//-------------------------------------------------------------------------------------------------

  Logger.log('~~~ CURRENT PROPERTIES STORED ~~~');
  return triggerID;

现在您必须停止脚本。您可以使用throw 来执行此操作。这是我的做法:

if (typeof execProperties.nextPageId !== 'undefined' && execProperties.nextPageId) 
  if (isTimeUp(startTime, Math.max(averageExec, execTime)) === true) 
    autoTrigger(execProperties);
    throw new Error('Script reseting');
  

我留 1 分钟以防万一。

function isTimeUp(start, need)   
  var cutoff = 500000 // in miliseconds (5 minutes)
  var now = new Date();
  return cutoff - (now.getTime() - start.getTime()) < need; 

请注意,在我的情况下,脚本不会调用相同的函数,因为有不同的启动过程。您需要做的是调整脚本以获取存储在属性中的值。您可以进一步阅读属性here。因此,如果您的脚本可以找到属性,您要做的就是删除触发器、检索变量并清除所有这些属性,然后一直跳到循环。这可以通过几个if 语句轻松完成。

希望这会有所帮助。

【讨论】:

以上是关于Google 应用程序脚本 360 秒超出时间限制解决方法的主要内容,如果未能解决你的问题,请参考以下文章

尝试在我的网络应用上使用 Google 和登录时,不断收到“超出未经身份验证使用的每日限制。继续使用需要注册”

批量查询超出速率限制

批量查询超出速率限制

Android Studio Google JAR 文件导致 GC 开销限制超出错误

Google BigQuery 速率限制 Tableau

在Swift中使用Google Calendar API时收到错误“超出未经身份验证的每日限制”