0

我正在尝试在 SQL Server 上运行多个 DDL(大约 90 个)。

DDL 不包含对表的任何更改,仅包含视图、存储过程和函数。DDL 之间可能存在相互依赖关系,例如,一个 STP 调用另一个 STP。

我不想以正确的顺序开始组织文件,因为这会花费太长时间,并且如果任何一个脚本出现错误,我希望整个操作都失败。

我怎样才能做到这一点?

到目前为止,我的想法是启动一个事务,告诉 SQL 忽略错误(我不知道该怎么做)运行一次所有脚本,告诉 SQL 再次开始抛出错误,再次运行所有脚本,然后如果一切成功,则提交。

  1. 这是一个好主意吗?
  2. CREATE \ ALTER即使有错误,我如何存储过程或查看?

为了澄清和解决一些问题......

这不适用于生产。我只是不想离开我正在测试的数据库损坏。

我想要实现的是:在服务器上运行一大组脚本,而无需花时间订购它们。但是,如果任何脚本中有错误,我想回滚整个操作。

我不关心隔离,我只希望操作作为单个事务发生。

4

3 回答 3

4

以正确的顺序组织文件,在测试环境中测试程序,进行验证和验收测试,然后在生产中运行它。

虽然在事务中运行 DDL 似乎可行,但实际上并非如此。有许多 DDL 语句不能很好地与事务混合。您必须使应用程序脱机,在架构更改之前进行数据库备份(或创建快照),运行经过测试和验证的升级过程(您的脚本),通过验收测试验证结果,然后将应用程序重新联机。如果出现故障,请恢复到最初创建的备份(具有与任何下游日志使用者相关的所有含义,例如复制、日志传送或镜像)。

这是正确的方法,就我而言,这是唯一的方法。我知道您会找到很多关于如何以错误方式执行此操作的建议。

于 2013-06-20T11:00:09.127 回答
1

我们实际上做了这样的事情来将我们的数据库脚本部署到生产环境中。我们在连接到我们数据库的应用程序中执行此操作。更复杂的是,我们还有 600 个数据库应该具有相同的模式,但实际上并没有。这是我们的方法:

  1. 将我们所有的脚本合并到一个大文件中。在每个文件之间注入 go's。这使它看起来像是一个很长的脚本。我们根据编码员的要求进行简单的订购。
  2. 将所有内容拆分为“go blocks”。由于 go 不是合法的 sql,我们将它们分成多个块,一次执行一个。
  3. 打开数据库连接。
  4. 开始交易。
  5. 对于每个 go 块:
    1. 确保交易仍然有效。(这非常重要。我稍后会解释原因。)
    2. 运行代码,记录错误。
  6. 如果有任何错误,则回滚。否则,提交。

在我们的多数据库设置中,我们将整个事情做了两次。对每个数据库运行一次,“测试”代码以确保任何数据库都没有错误,然后返回并再次“真正地”运行它们。

现在谈谈为什么您需要确保交易仍然有效。有一些命令会在出错时回滚您的事务!想象一下我们第一次发现这一点时的惊讶......错误之前的所有内容都被回滚,但之后的所有内容都已提交。但是,如果出现错误,则同一个块中的任何内容都不会被提交,所以一切都很好。

下面是我们执行代码的核心。我们在 SqlClient 周围使用了一个包装器,但它看起来应该与 SqlClient 非常相似。

Dim T = New DBTransaction(client)
For Each block In scriptBlocks
    If Not T.RestartIfNecessary Then
        exceptionCount += 1
        Log("Could not (re)start the transaction for {0}. Not executing the rest of the script.", scriptName)
        Exit For
    End If
    Debug.Assert(T.IsInTransaction)
    Try
        client.Text = block
        client.ExecNonQuery()
    Catch ex As Exception
        exceptionCount += 1
        Log(ex.Message + " on {0} executing: '{1}'", client.Connection.Database, block.Replace(vbNewLine, ""))
    End Try
Next
If exceptionCount > 0 Then Log("There were {0} exceptions while executing {1}.", exceptionCount, scriptName)
If testing OrElse
    exceptionCount > 0 Then
    Try
        T.Rollback()
        Log("Rolled back all changes for {0} on {1}.", scriptName, client.Connection.Database)
    Catch ex As Exception
        Log("Could not roll back {0} on {1}: {2}", scriptName, client.Connection.Database, ex.Message)
        If Debugger.IsAttached Then
            Debugger.Break()
        End If
    End Try

Else
    T.Commit()
    Log("Successfully committed all changes for {0} on {1}.", scriptName, client.Connection.Database)
End If
Return exceptionCount




Class DBTransaction
    Private _tName As String
    Public ReadOnly Property name() As String
        Get
            Return _tName
        End Get
    End Property
    Private _client As OB.Core2.DB.Client
    Public Sub New(client As OB.Core2.DB.Client, Optional name As String = Nothing)
        If name Is Nothing Then
            name = "T" & Guid.NewGuid.ToString.Replace("-", "").Substring(0, 30)
        End If
        _tName = name
        _client = client
    End Sub
    Public Function Begin() As Boolean
        Return RestartIfNecessary()
    End Function
    Public Function RestartIfNecessary() As Boolean
        Try
            _client.Text = "IF NOT EXISTS (Select transaction_id From sys.dm_tran_active_transactions where name = '" & name & "') BEGIN BEGIN TRANSACTION " & name & " END"
            _client.ExecNonQuery()
            Return IsInTransaction()
        Catch ex As Exception
            Return False
        End Try
    End Function
    Public Function IsInTransaction() As Boolean
            _client.Text = "Select transaction_id From sys.dm_tran_active_transactions where name = '" & name & "'"
            Dim scalar As String = _client.ExecScalar
            Return scalar <> ""
    End Function
    Public Sub Rollback()
        _client.Text = "ROLLBACK TRANSACTION " & name
        _client.ExecNonQuery()
    End Sub
    Public Sub Commit()
        _client.Text = "COMMIT TRANSACTION " & name
        _client.ExecNonQuery()
    End Sub
End Class
于 2013-06-27T18:30:12.493 回答
0

你有一个很好的答案,这里是“hack”的答案。对于“你不能这样做,但如果你非常想要,那就继续”的情况。我非常有信心你不会实现你的想法,因此

做完整备份!

假设这些文件中没有COMMITorGO语句(显式或!隐式!),您唯一需要做的就是在单个事务中运行它们。将它们组合在一个文件中,包装在一个事务中,然后运行。

如何将 90 个文件合并到 1 个文件中:

如果按名称排序使它们按正确的顺序排列,则在命令提示符下从包含文件的文件夹中运行它:

FOR /F "tokens=1" %G IN ('dir /b /-d /o:n *.sql') DO (
    type %G >> Big_SQL_Script.sql && echo. >> Big_SQL_Script.sql 
    )

如果顺序是随机的,则创建一个文件列表dir /b /-d *.sql > File_Name_List.txt并手动对其进行排序。然后运行:

FOR /F "tokens=1" %G IN (File_Name_List.txt) DO (
    type %G >> Big_SQL_Script.sql && echo. >> Big_SQL_Script.sql 
    )

通过这种方式,您可以按自动顺序连接 90 个文件。跑,看看会发生什么。

祝你好运!

于 2013-06-26T21:20:39.650 回答