【问题标题】:Azure Devops Build Task executing SQL script aborts after a number of sp_rename calls执行 SQL 脚本的 Azure Devops 构建任务在多次 sp_rename 调用后中止
【发布时间】:2020-05-28 08:11:31
【问题描述】:

我想构建一个 Azure Devops 构建任务,该任务针对 SQL Server 2017 数据库执行一系列 SQL 脚本。我按照本教程构建任务:https://docs.microsoft.com/en-us/azure/devops/extend/develop/add-build-task?view=azure-devops

该任务通常会成功运行,并且我已经针对我的本地数据库(在 SQL Server 2017 Express 中)执行了各种脚本。我正在使用 npm 包“mssql/msnodesqlv8”(mssql 的本机 SQL Server 驱动程序)直接在 node.js 中连接和执行脚本。

async function executeBatches(script: string, pool: sql.ConnectionPool) {
    const batches = script.split("\r\nGO");
    for (const batch of batches) {
        console.log("Executing script:", batch);
        await pool.batch(batch);
    }
}

现在我发现一个脚本以一种非常奇怪的方式失败。有问题的脚本在事务中对 4 个表执行多次重命名,如下所示:

BEGIN TRANSACTION
EXEC sp_rename 'table' , 'newTable'
EXEC sp_rename 'newTable.column', 'newColumn', 'COLUMN'
(repeat for several columns)
EXEC sp_rename 'dbo.PK_table', 'PK_newTable'

(repeat for 3 more tables)
COMMIT

在 SQL Server Management Studio 中,脚本可以正确执行。但在 Devops 任务中,此脚本在大约 18 次 sp_rename 调用后中止。没有抛出错误,事务保持打开状态。客户端将继续运行(因为它没有错误),在执行更多查询后,SQL Server 会启动回滚,当然会回滚自执行此脚本以来的所有更改。

我切换了脚本中的语句并尝试注释掉一些行,但它总是在大约 18 次 sp_rename 调用后中止。当我删除足够多的行以便有 18 个或更少的 sp_rename 调用时,该任务可以完全运行脚本并提交更改(不管哪一行)。

当我删除事务时,它会执行所有重命名,直到那个神奇的数字,然后仍然中止脚本并让最后一条语句的隐式事务保持打开状态,因此它仍然会在更多查询后回滚所有更改。

我运行了 SQL Profiler,它显示 StmtStarting 进行重命名,然后 BatchCompleted 出现错误“2 - Abort”,但没有显示其他错误或原因说明批处理被中止的原因。

执行脚本时,system_health 会话显示 2 个错误:

错误 #1

tds_flags "DisconnectDueToReadError, NetworkErrorFoundInInputStream, NormalDisconnect" 和 tds_input_buffer_error 109 的连接错误。

错误 #2

error_code 5023 的安全错误(表示“组或资源未处于执行请求操作的正确状态。”)

在线搜索这些错误没有得到可用的结果,因为它们要么没有解决方案,要么与登录问题有关,我认为情况并非如此,因为我可以很好地执行其他脚本。

我还检查了编码,并且通过节点“fs”库正确读取了脚本。

如果我能找到导致此问题的原因的任何帮助或指示,我们将不胜感激。

编辑:我开始使用 msnodesqlv8 构建一个较小的示例。

import tl = require("azure-pipelines-task-lib/task");
import fs = require("fs");
import path = require("path");
import util = require("util");

import { SqlClient } from "msnodesqlv8";
// tslint:disable-next-line: no-var-requires
const sqlClient: SqlClient = require("msnodesqlv8");
const open = util.promisify(sqlClient.open);
const query = util.promisify(sqlClient.query);

async function run() {
    try {
        const scriptDirectory = tl.getInput("ScriptDirectory", true) ?? "";
        const connectionString = "Driver={ODBC Driver 13 for SQL Server};Server={.\\SQLEXPRESS};Uid={sa};Pwd={start};Database={MyDatabase};Encrypt={yes};TrustServerCertificate={yes}";
        const scriptsDir = fs.readdirSync(scriptDirectory);
        const con = await open(connectionString);
        const close = util.promisify(con.close);
        try {
            const conQuery = util.promisify(con.query);
            for (const file of scriptsDir) {
                console.log("Executing:", file);
                const script = readFileWithoutBom(path.join(scriptDirectory, file));
                console.log("Executing script:", script);
                // await query(connectionString, { query_str: script, query_timeout: 120 });
                await conQuery({ query_str: script, query_timeout: 120 });

                const insert = `INSERT INTO AppliedDatabaseScript (ScriptFile, DateApplied) VALUES ('${file}', GETDATE())`
                console.log("Executing script:", insert);
                // await query(connectionString, { query_str: insert });
                await conQuery({ query_str: insert });
            }
        } finally {
            await close();
        }
    }
    catch (err) {
        console.error(err);
        tl.setResult(tl.TaskResult.Failed, err.message);
    }
}

// Strip BOM. SQL Server won't execute certain scripts with BOM.
function readFileWithoutBom(filePath: string) {
   return fs.readFileSync(filePath, "utf-8").replace(/^\uFEFF/, "");
}

行为仍然相同。我尝试为每个查询使用公共连接和单独的连接。它将回滚同一连接中的所有内容并继续,就好像没有发生错误一样。我还摆弄了查询超时,但根本与错误无关。

【问题讨论】:

  • 在您的数据库上执行 18 个 sp_renames 需要多长时间?大约90秒?听起来像一个 CommandTimeout 问题,尽管它为什么不(至少开始)立即回滚事务是一个谜。 msnodesqlv8 调用此 query_timeout 并没有提及默认值,但在 .NET 和其他平台中通常约为 90 秒。
  • @AlwaysLearning 该脚本几乎立即执行(不到 1 秒)。它绝对不会超时。
  • 根据您的描述,如果 sp_rename 调用少于 18 次,则该任务可以正常工作。这似乎与超时有关。 mssql/msnodesqlv8 包有两个时间限制:conn_timeout :2query_timeout : 2。这两个步骤似乎有 2 秒的限制。您可以检查这两个步骤是否已超时。这是关于the package 的文档。
  • 很抱歉造成混淆,但我没有直接使用 msnodesqlv8,而是使用来自 mssql package 的包装器 mssql/msnodesqlv8。我还用直接链接更新了帖子。正如我之前所说,这个查询在不到一秒的时间内执行,我还有其他更复杂、更长的查询可以正常执行。我可以尝试只用 msnodesqlv8 重写它,但这需要一些时间。

标签: node.js sql-server typescript azure-devops sql-server-2017


【解决方案1】:

我设法通过切换到非本地驱动程序tedious(也受mssql 支持)来使其工作。 node.js 的本机 SQL Server 驱动程序似乎无法正常工作。

如果有人在连接繁琐时遇到问题(因为我在网上找不到合适的文档),这些是基本的配置选项:

{
    user: "username",
    password: "password",
    server: "hostname\\instancename",
    database: "database",
    port: 1433
}

您需要通过在 SQL Server 配置管理器中激活 TCP/IP 来确保 SQL Server Browser 正在运行并且实例配置为远程访问(即使通过 localhost 连接)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-12-09
    • 2019-04-14
    • 2016-10-26
    • 2020-01-18
    • 1970-01-01
    • 1970-01-01
    • 2016-07-01
    相关资源
    最近更新 更多