Làm việc với MS SQL từ Powershell trên Linux

Bài viết này hoàn toàn thực tế và dành riêng cho câu chuyện buồn của tôi

Chuẩn bị sẵn sàng cho Sản phẩm không chạm đối với RDS (MS SQL), thứ mà tất cả chúng tôi đều xôn xao, tôi đã trình bày (POC - Proof Of Concept) về tự động hóa: một tập hợp các tập lệnh powershell. Sau buổi thuyết trình, khi những tràng pháo tay kéo dài như vũ bão lắng xuống, chuyển thành tiếng vỗ tay không ngừng, họ nói với tôi - tất cả điều này đều tốt, nhưng chỉ vì lý do ý thức hệ, tất cả nô lệ Jenkins của chúng tôi đều làm việc trên Linux!

Điều này có thể thực hiện được không? Lấy một DBA ấm áp như vậy từ Windows và đặt nó vào sức nóng của powershell trong Linux? Đây không phải là tàn nhẫn sao?

Làm việc với MS SQL từ Powershell trên Linux
Tôi đã phải đắm mình trong sự kết hợp kỳ lạ của công nghệ này. Tất nhiên, tất cả hơn 30 tập lệnh của tôi đều ngừng hoạt động. Thật ngạc nhiên, tôi đã cố gắng khắc phục mọi thứ trong một ngày làm việc. Tôi đang viết trong sự theo đuổi nóng bỏng. Vậy bạn có thể gặp phải những cạm bẫy nào khi chuyển tập lệnh powershell từ Windows sang Linux?

sqlcmd vs Gọi-SqlCmd

Hãy để tôi nhắc bạn về sự khác biệt chính giữa chúng. Tiện ích cũ tốt sqlcmd Nó cũng hoạt động trên Linux với chức năng gần như giống hệt nhau. Chúng tôi chuyển truy vấn để thực thi -Q, tệp đầu vào là -i và đầu ra là -o. Nhưng tất nhiên, tên tệp được phân biệt chữ hoa chữ thường. Nếu bạn sử dụng -i thì trong tệp hãy ghi vào cuối:

GO
EXIT

Nếu không có EXIT ở cuối thì sqlcmd sẽ tiến hành chờ nhập liệu, còn nếu trước đó EXIT sẽ không GO, thì lệnh cuối cùng sẽ không hoạt động. Tệp đầu ra chứa tất cả đầu ra, lựa chọn, thông báo, in, v.v.

Invoke-SqlCmd tạo ra kết quả dưới dạng DataSet, DataTables hoặc DataRows. Do đó, nếu bạn xử lý kết quả của một lựa chọn đơn giản, bạn có thể sử dụng sqlcmd, sau khi phân tích kết quả đầu ra của nó, hầu như không thể rút ra được điều gì đó phức tạp: vì điều này có Gọi-SqlCmd. Nhưng đội này cũng có những trò đùa riêng:

  • Nếu bạn chuyển một tập tin cho cô ấy qua -Tệp đầu vàosau đó EXIT không cần thiết, hơn nữa nó còn tạo ra lỗi cú pháp
  • -Tệp đầu ra không, lệnh trả về cho bạn kết quả dưới dạng đối tượng
  • Có hai cú pháp để chỉ định một máy chủ: -ServerInstance -Tên người dùng -Mật khẩu -Cơ sở dữ liệu và thông qua -Chuỗi kết nối. Thật kỳ lạ, trong trường hợp đầu tiên không thể chỉ định cổng nào khác ngoài 1433.
  • đầu ra văn bản, gõ PRINT, đơn giản là "bắt" sqlcmdĐối với Gọi-SqlCmd là một vấn đề
  • Và quan trọng nhất: Rất có thể Linux của bạn không có lệnh ghép ngắn này!

Và đây là vấn đề chính. Lệnh ghép ngắn này chỉ có trong tháng XNUMX đã có sẵn cho các nền tảng không phải Windows, và cuối cùng chúng ta có thể tiến về phía trước!

thay thế biến

sqlcmd có sự thay thế biến bằng cách sử dụng -v, ví dụ như thế này:

# $conn содержит начало команды sqlcmd
$cmd = $conn + " -i D:appsSlaveJobsKillSpid.sql -o killspid.res 
  -v spid =`"" + $spid + "`" -v age =`"" + $age + "`""
Invoke-Expression $cmd

Trong tập lệnh SQL, chúng tôi sử dụng các thay thế:

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

Vì vậy, nó ở đây. Trong *nix thay thế biến không hoạt động... Tham số -v làm ngơ. bạn Gọi-SqlCmd làm ngơ -Biến. Mặc dù tham số chỉ định chính các biến bị bỏ qua, nhưng các thay thế tự nó hoạt động—bạn có thể sử dụng bất kỳ biến nào từ Shell. Tuy nhiên, tôi cảm thấy khó chịu với các biến số và quyết định không phụ thuộc vào chúng chút nào, đồng thời hành động một cách thô lỗ và thô sơ, vì các tập lệnh SQL rất ngắn:

# 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"

Như bạn hiểu, đây là một bản thử nghiệm đã có từ phiên bản Unix.

Tải lên tệp

Trong phiên bản Windows, mọi thao tác đều đi kèm với quá trình kiểm tra: chúng tôi đã chạy sqlcmd, nhận được một số loại lạm dụng trong tệp đầu ra, đã đính kèm tệp này vào bảng kiểm tra. May mắn thay, máy chủ SQL hoạt động trên cùng một máy chủ với Jenkins, nó được thực hiện như thế này:

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 = '' '',CODEPAGE = ''ACP'')'
  exec (@sql)
  select @sql=filer from #multi
  update JenkinsAudit set multiliner=@sql where ID=@id
  return

Do đó, chúng tôi nuốt toàn bộ tệp BCP và chuyển nó vào trường nvarchar(max) của bảng kiểm tra. Tất nhiên, toàn bộ hệ thống này đã sụp đổ, bởi vì thay vì máy chủ SQL, tôi nhận được RDS và BULK INSERT hoàn toàn không hoạt động thông qua UNC do cố gắng khóa độc quyền trên một tệp và với RDS, điều này thường bị loại khỏi sự khởi đầu. Vì vậy tôi quyết định thay đổi thiết kế hệ thống, lưu trữ từng dòng kiểm tra:

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

Và viết vào bảng này như thế này:

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() 
  }  

Để chọn nội dung, bạn cần chọn theo ID, chọn theo thứ tự n (danh tính).

Trong bài viết tiếp theo, tôi sẽ đi vào chi tiết hơn về cách tất cả những thứ này tương tác với Jenkins.

Nguồn: www.habr.com

Thêm một lời nhận xét