使用PowerShell排错----使用PowerShell调校SQL Server性能

这里会测试一些脚本,用于SQL Server调校,如索引维护、管理磁盘空间和导出DDL脚本,最后看看如何排Job来自动运行PowerShell脚本。

索引维护

要确保最佳性能,索引维护是一项重要的工作。传统地,DBA会写一些T-SQL来执行索引维护,但这并非容易的任务。PowerShell与SQL Server SMO库一起,提供了一个简练的解决方案用于索引维护。下面的脚本显示了如何如何通过执行PowerShell来完成广泛的索引维护日常工作。该脚本遍历用户数据库中所有表的所有索引,并基于索引的碎片水平来执行3项任务中的一个(代码文件:PS_ManageFragmentation01.PS1):

  • 如果索引碎片低于5%,则不作任何事情,因为对性能的影响微不足道;
  • 如果索引碎片位于5%和30%之间,则执行索引重组(reorganization);
  • 如果索引碎片高于30%,则执行索引重建(rebuild)。
[string] $ServerName = $args[0]
[string] $TargetDatabaseName = $args[1]
[Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | out-null
$server = New-Object Microsoft.SqlServer.Management.Smo.Server $ServerName
$targetDB = $server.Databases[$targetDatabaseName]
foreach ($table in $targetDB.Tables)
{
	foreach($index in $table.Indexes)
	{
		$fragmentation = $index.EnumFragmentation()
		$averageFragmentation = $fragmentation.Rows[0].AverageFragmentation
		if($averageFragmentation -lt .05)
		{
			continue
		}
		if($averageFragmentation -ge .05 -and $averageFragmentation -lt .3)
		{
			$index.Reorganize()
			continue
		}
		$index.Rebuild()
	}
}

管理备份的磁盘空间使用率

使用PowerShell操作文件系统很简单。下面的例子中,创建了一个脚本来删除2天前的事务日志备份、8天前的差异备份,91天前的完整备份。基于文件扩展名和最后一次写入时间,把查询分成了3份,根据当前日期减去天数来定义各种备份类型的保存期(代码文件:PS_DeleteOldBackups01.PS1):

Get-ChildItem .\Backups -include *.trn -recurse '
| Where-Object { $_.lastwritetime -lt (Get-Date).AddDays(-2) } '
| Remove-Item
Get-ChildItem .\Backups -include *.dif -recurse '
| Where-Object { $_.lastwritetime -lt (Get-Date).AddDays(-8) } '
| Remove-Item
Get-ChildItem .\Backups -include *.bak -recurse '
| Where-Object { $_.lastwritetime -lt (Get-Date).AddDays(-91) } '
| Remove-Item

使用SMO提取DDL

错位索引或表定义不佳两种情况会有严重的性能影响,需要改变数据库DDL。处于这种考虑,我创建了下面的脚本,它可以排Job一天运行一次,从数据库中提取所有的DDL对象,并存放到磁盘中以提取时间命名的文件夹中。如果发现性能不佳,按天比对文件,来识别是不是因为DDL改变而引起数据库性能下降。这个脚本很长,但它组合了前面所讲的所有的概念及技术,把所有的数据库对象导入到文件(代码文件:PS_ExtractDDL01.PS1):

#Helper function to script the DDL Object to disk
function Write-DDLOutput ($filename, $object)
{
	New-Item $filename -type file -force | Out-Null
	#Specify the filename
	$ScriptingOptions.FileName = $filename
	#Assign the scripting options to the Scripter
	$Scripter.Options = $ScriptingOptions
	#Script the index
	$Scripter.Script($object)
}

#Load the SMO assembly
[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | out-null
#Create all the global vars we need
$Server = New-Object ("Microsoft.SqlServer.Management.Smo.Server")
$Scripter = New-Object ("Microsoft.SqlServer.Management.Smo.Scripter")
$ScriptingOptions = New-Object
("Microsoft.SqlServer.Management.SMO.ScriptingOptions")
$Scripter.Server = $Server
#Specifies the root folder that we'll store the Scripts into This will probably
become a param in future
$RootBackupFolder = "C:\SqlBackups\DDL"
#Get the day of the week so that we can create a folder for each day
$Today = [System.DateTime]::Today.DayOfWeek
#Store today's backup folder
$DDLBackupFolder = Join-Path -Path $RootBackupFolder -ChildPath $Today
#Check if today's folder exists
if ([System.IO.Directory]::Exists($DDLBackupFolder))
{
	#If it does delete it's contents
	Remove-Item (Join-Path -Path $DDLBackupFolder -ChildPath *) -Recurse
}
else
{
	#Otherwise create it
	[System.IO.Directory]::CreateDirectory($DDLBackupFolder) | Out-Null
}
	
#Setup the scripting options
$ScriptingOptions.AppendToFile = $true
$ScriptingOptions.FileName = $filename
$ScriptingOptions.ToFileOnly = $true
$ScriptingOptions.ScriptData = $false
#Loop through all the databases to script them out
foreach ($database in ($Server.databases | where {$_.IsSystemObject -eq $false -and
$_.IsDatabaseSnapshot -eq $false}))
{
	$databaseBackupFolder = Join-Path -Path $DDLBackupFolder -ChildPath
	$Database.Name
	#This will be the database create script
	Write-DDLOutput (Join-Path -Path ($databaseBackupFolder) -ChildPath
	($Database.Name + ".sql")) $database
	$ProgrammabilityBackupFolder = Join-Path -Path
	$databaseBackupFolder -ChildPath "Programmability"
	$DefaultsBackupFolder = Join-Path -Path
	$ProgrammabilityBackupFolder -ChildPath "Defaults"
	foreach ($default in $database.Defaults)
	{
		#Generate a filename for the default
		Write-DDLOutput (Join-Path -Path
		($DefaultsBackupFolder) -ChildPath
		($default.Schema + "." + $default.Name + ".sql"))
		$default
	}
	#Create a folders to store the functions in
	$FunctionsBackupFolder = Join-Path -Path
	$ProgrammabilityBackupFolder -ChildPath "Functions"
	$ScalarFunctionsBackupFolder = Join-Path -Path
	$FunctionsBackupFolder -ChildPath "Scalar-valued Functions"
	$TableValuedFunctionsBackupFolder = Join-Path -Path
	$FunctionsBackupFolder -ChildPath "Table-valued Functions"
	foreach ($function in $database.UserDefinedFunctions | where
	{$_.IsSystemObject -eq $false})
	{
		#script the functions into folders depending upon type. We're
		only interested in scalar and table
		switch ($function.FunctionType)
		{
			scalar
			{
				#Generate a filename for the scalar function
				$filename = Join-Path -Path
				($ScalarFunctionsBackupFolder) -ChildPath
				($function.Schema + "." + $function.Name + ".sql")
			}
			table
			{
				#Generate a filename for the table value function
				$filename = Join-Path -Path
				($TableValuedFunctionsBackupFolder) -ChildPath
				($function.Schema + "." + $function.Name + ".sql")
			}
			default { continue }
		}
		#Script the function
		Write-DDLOutput $filename $function
	}
	$RulesBackupFolder = Join-Path -Path
	$ProgrammabilityBackupFolder -ChildPath "Rules"
	foreach ($rule in $database.Rules)
	{
		#Script the rule
		Write-DDLOutput (Join-Path -Path
		($RulesBackupFolder) -ChildPath
		($rule.Schema + "." + $rule.Name + ".sql")) $rule
	}
	#Create a folder to store the Sprocs in
	$StoredProceduresBackupFolder = Join-Path -Path
	$ProgrammabilityBackupFolder -ChildPath "Stored Procedures"
	#Loop through the sprocs to script them out
	foreach ($storedProcedure in $database.StoredProcedures | where
	{$_.IsSystemObject -eq $false})
	{
		#script the sproc
		Write-DDLOutput ($filename = Join-Path -Path
		($StoredProceduresBackupFolder) -ChildPath
		($storedProcedure.Schema + "." +
		$storedProcedure.Name + ".sql"))
		$storedProcedure
	}
	#Create a folder to store the table scripts
	$TablesBackupFolder = Join-Path -Path $databaseBackupFolder -ChildPath
	"Tables"
	$TableIndexesBackupFolder = Join-Path -Path
	$TablesBackupFolder -ChildPath "Indexes"
	$TableKeysBackupFolder = Join-Path -Path
	$TablesBackupFolder -ChildPath "Keys"
	$TableConstraintsBackupFolder = Join-Path -Path
	$TablesBackupFolder -ChildPath "Constraints"
	$TableTriggersBackupFolder = Join-Path -Path
	$TablesBackupFolder -ChildPath "Triggers"
	#Loop through the tables to script them out
	foreach ($table in $database.Tables | where
	{$_.IsSystemObject -eq $false})
	{
		#Script the Table
		Write-DDLOutput (Join-Path -Path
		($TablesBackupFolder) -ChildPath
		($table.Schema + "." + $table.Name + ".sql")) $table
		foreach($Constraint in $table.Checks)
		{
			#Script the Constraint
			Write-DDLOutput (Join-Path -Path
			($TableConstraintsBackupFolder) -ChildPath
			($table.Schema + "." + $table.Name + "." +
			$Constraint.Name + ".sql")) $Constraint
		}
		foreach ($index in $table.Indexes)
		{
			#Generate a filename for the table
			switch($index.IndexKeyType)
			{
				DriPrimaryKey
				{
					$filename = Join-Path -Path
					($TableKeysBackupFolder) -ChildPath
					($table.Schema + "." +
					$table.Name + "." +
					$index.Name + ".sql")
				}
				default
				{
					$filename = Join-Path -Path
					($TableIndexesBackupFolder) -ChildPath
					($table.Schema + "." +
					$table.Name + "." +
					$index.Name + ".sql")
				}
			}
			#Script the index
			Write-DDLOutput $filename $index
		}
		foreach ($trigger in $table.Triggers)
		{
			#Script the trigger
			Write-DDLOutput (Join-Path -Path
			($TableTriggersBackupFolder) -ChildPath
			($table.Schema + "." + $table.Name + "." +
			$trigger.Name + ".sql")) $trigger
		}
	}
	#Create a folder to store the view scripts
	$ViewsBackupFolder = Join-Path -Path $databaseBackupFolder -ChildPath
	"Views"
	$ViewKeysBackupFolder = Join-Path -Path $ViewsBackupFolder -ChildPath
	"Keys"
	$ViewIndexesBackupFolder = Join-Path -Path
	$ViewsBackupFolder -ChildPath "Indexes"
	$ViewTriggersBackupFolder = Join-Path -Path
	$ViewsBackupFolder -ChildPath "Triggers"
	#Loop through the views to script them out
	foreach ($view in $database.Views | where
	{$_.IsSystemObject -eq $false})
	{
		#Script the view
		Write-DDLOutput (Join-Path -Path
		($ViewsBackupFolder) -ChildPath
		($view.Schema + "." + $view.Name + ".sql")) $view
		foreach ($index in $view.Indexes)
		{
			#Generate a filename for the table
			switch($index.IndexKeyType)
			{
				DriPrimaryKey
				{
					$filename = Join-Path -Path
					($ViewKeysBackupFolder) -ChildPath
					($view.Schema + "." +
					$view.Name + "." + $index.Name + ".sql")
				}
				default
				{
					$filename = Join-Path -Path
					($ViewIndexesBackupFolder) -ChildPath
					($view.Schema + "." + $view.Name + "." +
					$index.Name + ".sql")
				}
			}
			Write-DDLOutput $filename $index
		}
		foreach ($trigger in $view.Triggers)
		{
			#Script the trigger
			Write-DDLOutput (Join-Path -Path
			($ViewTriggersBackupFolder) -ChildPath
			($view.Schema + "." + $view.Name + "." +
			$trigger.Name + ".sql")) $trigger
		}
	}
}



脚本执行排Job
有两种方式对脚本执行排Job。第一种是使用Windows Task Scheduler,如果你没有安装SQL  Server且希望执行PowerShell脚本时,用这种方式很有用。你可以很容易添加一项新任务到Scheduler,并执行PowerShell.exe, 把你要执行的脚本作为参数传给它。

对于安装SQL Server 2008 R2或更高版本的服务器而言,你还可以通过SQL Server Agent Job来执行PowerShell。通过新建Job,并在Type下拉框里选择PowerShell,可以很容易实现。然后数据PowerShell脚本到Commend文本框。

不幸的是,在SQL Server 2008 R2里使用PowerShell Job不是非常有用,因为它不能调用PowerShell 1.0,所以很多脚本和模块不能正常工作。如果你想在SQL Server 2008 R2中执行PowerShell 2.0脚本,你最好使用前面讲的执行PowerShell.exe的方式。幸运的是,这个问题在SQL Server 2012中解决了,因为他装载了PowerShell 2.0。

SQL Server代理Job的优势是你或许已经有Job在跑,这种方式能够让你在一个地方管理所有的Job。你也可以使用嵌入在SQL Job引擎里的日志功能来监控PowerShell脚本的执行情况。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值