在Linux上使用Powershell处理MS SQL

本文纯粹是实用的,致力于我的悲伤故事。


为了准备让RDS(MS SQL) 零接触的PROD ,我们的耳朵都在嗡嗡作响,我做了一个关于自动化的演示(POC-概念验证):一组Powershell脚本。 演讲结束后,当长时间的热烈掌声渐渐消退,并不断鼓掌时,我被告知这一切都很好,但是仅出于意识形态的原因,我们所有人都在Linux下运行Jenkins奴隶!

有可能吗 要从Windows下获得如此热情的DBA,并使其在Linux下非常强大的功能中呢? 那不是很残忍吗?


我不得不深入研究这种奇怪的技术组合。 当然,我所有的30多个脚本都停止了工作。 令我惊讶的是,在一个工作日内,我设法解决了所有问题。 我正在紧追写作。 因此,将Powershell脚本从Windows移植到Linux时,会遇到什么陷阱?

sqlcmd与Invoke-SqlCmd


让我提醒您它们之间的主要区别。 好的旧sqlcmd实用程序可以在Linux下运行,并且功能几乎相同。 要执行查询,我们传递-Q,输入文件为-i,输出为-o。 当然,这里只是文件名,区分大小写。 如果使用-i,则在末尾写入文件:

GO EXIT 

如果结尾没有EXIT,则sqlcmd将等待输入,如果EXIT之前没有GO ,则最后一条命令将无效。 所有输出,选择,消息,打印等,都进入输出文件。

Invoke-SqlCmd将结果作为DataSet,DataTables或DataRows返回。 因此,如果您可以通过sqlcmd处理简单选择的结果, 解析其输出,然后输出复杂的内容几乎是不可能的:为此有Invoke-SqlCmd 。 但是这支球队有自己的笑话:

  • 如果通过-InputFile将文件传递给它,则不需要EXIT ,此外,它还会产生语法错误
  • -OutputFile不是,该命令将结果作为对象返回
  • 用于指定服务器的语法有两种: -ServerInstance -Username -Password -Database和通过-ConnectionString 。 奇怪的是,在第一种情况下,您不能指定1433以外的端口。
  • Invoke-SqlCmd遇到 的问题是PRINT类型的文本输出,它被sqlcmd “捕获”
  • 最重要的是: 此cmdlet最有可能不在您的Linux中!

这是主要问题。 仅在三月份,此cmdlet 才可用于非Windows平台 ,最后我们可以继续前进!

变量替代


Sqlcmd使用-v进行变量替换,如下所示:

 # $conn    sqlcmd $cmd = $conn + " -i D:\apps\SlaveJobs\KillSpid.sql -o killspid.res -v spid =`"" + $spid + "`" -v age =`"" + $age + "`"" Invoke-Expression $cmd 

在SQL脚本中,我们使用替换:

 set @spid=$(spid) set @age=$(age) 

所以在这里。 在* nix中,变量替换无效-v选项将被忽略。 Invoke-SqlCmd忽略-Variables 。 尽管设置变量本身的参数将被忽略,但是替换本身也可以工作-您可以使用Shell中的任何变量。 但是,我对变量感到恼火,并决定根本不依赖它们,并且由于sql的脚本很短,因此我的行为举止粗俗:

 # prepend the parameters "declare @age int, @spid int" | Add-Content "q.sql" "set @spid=" + $spid | Add-Content "q.sql" "set @age=" + $age | Add-Content "q.sql" foreach ($line in Get-Content "Sqlserver/Automation/KillSpid.sql") { $line | Add-Content "q.sql" } $cmd = "/opt/mssql-tools/bin/" + $conn + " -i q.sql -o res.log" 

如您所知,这是对Unix版本的测试。

上载档案


在Windows版本中,我执行的任何操作都伴随着审核:我们执行了sqlcmd,在输出文件中出现了某种滥用,并将此文件附加到审核板上。 幸运的是,SQL Server与Jenkins在同一服务器上工作,它是这样完成的:

 CREATE procedure AuditUpload @id int, @filename varchar(256) as set nocount on declare @sql varchar(max) CREATE TABLE #multi (filer NVARCHAR(MAX)) set @sql='BULK INSERT #multi FROM '''+@filename +''' WITH (ROWTERMINATOR = ''\0'',CODEPAGE = ''ACP'')' exec (@sql) select @sql=filer from #multi update JenkinsAudit set multiliner=@sql where ID=@id return 

因此,我们吞下了整个BCP文件,并在nvarchar(max)字段中插入了审核表。 当然,整个系统崩溃了,因为我没有使用SQL Server而是使用RDS,而BULK INSERT在\\ UNC上不起作用,因为它试图对文件进行独占锁定,而RDS最初注定要失败。 因此,我决定更改系统的设计,逐行存储审核:

 CREATE TABLE AuditOut ( ID int NULL, TextLine nvarchar(max) NULL, n int IDENTITY(1,1) PRIMARY KEY ) 

并像下面这样写入该表:

 function WriteAudit([string]$Filename, [string]$ConnStr, [string]$Tabname, [string]$Jobname) { # get $lastid of the last execution --    #create grid and populate it with data from file $audit = Get-Content $Filename $DT = new-object Data.DataTable $COL1 = new-object Data.DataColumn; $COL1.ColumnName = "ID"; $COL1.DataType = [System.Type]::GetType("System.Int32") $COL2 = new-object Data.DataColumn; $COL2.ColumnName = "TextLine"; $COL2.DataType = [System.Type]::GetType("System.String") $DT.Columns.Add($COL1) $DT.Columns.Add($COL2) foreach ($line in $audit) { $DR = $dt.NewRow() $DR.Item("ID") = $lastid $DR.Item("TextLine") = $line $DT.Rows.Add($DR) } # write it to table $conn=new-object System.Data.SqlClient.SQLConnection $conn.ConnectionString = $ConnStr $conn.Open() $bulkCopy = new-object ("Data.SqlClient.SqlBulkCopy") $ConnStr $bulkCopy.DestinationTableName = $Tabname $bulkCopy.BatchSize = 50000 $bulkCopy.BulkCopyTimeout = 0 $bulkCopy.WriteToServer($DT) $conn.Close() } 

要选择内容,请按ID选择,然后依次选择n(身份)。

在下一篇文章中,我将详细介绍这一切与詹金斯的互动方式。

Source: https://habr.com/ru/post/zh-CN447100/


All Articles