我们实际上做了这样的事情来将我们的数据库脚本部署到生产环境中。我们在连接到我们数据库的应用程序中执行此操作。更复杂的是,我们还有 600 个数据库应该具有相同的模式,但实际上并没有。这是我们的方法:
- 将我们所有的脚本合并到一个大文件中。在每个文件之间注入 go's。这使它看起来像是一个很长的脚本。我们根据编码员的要求进行简单的订购。
- 将所有内容拆分为“go blocks”。由于 go 不是合法的 sql,我们将它们分成多个块,一次执行一个。
- 打开数据库连接。
- 开始交易。
- 对于每个 go 块:
- 确保交易仍然有效。(这非常重要。我稍后会解释原因。)
- 运行代码,记录错误。
- 如果有任何错误,则回滚。否则,提交。
在我们的多数据库设置中,我们将整个事情做了两次。对每个数据库运行一次,“测试”代码以确保任何数据库都没有错误,然后返回并再次“真正地”运行它们。
现在谈谈为什么您需要确保交易仍然有效。有一些命令会在出错时回滚您的事务!想象一下我们第一次发现这一点时的惊讶......错误之前的所有内容都被回滚,但之后的所有内容都已提交。但是,如果出现错误,则同一个块中的任何内容都不会被提交,所以一切都很好。
下面是我们执行代码的核心。我们在 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