Skip to content

Commit 94e0708

Browse files
Invoke-DbaDbShrink - Add WAIT_AT_LOW_PRIORITY support via DBCC SHRINKFILE T-SQL
Replaces SMO $file.Shrink() calls with direct DBCC SHRINKFILE T-SQL to enable WAIT_AT_LOW_PRIORITY support (SQL Server 2022+). Adds -WaitAtLowPriority switch and -AbortAfterWait parameter (Self|Blockers). Includes version guard with Stop-Function for older instances. Updates parameter validation tests. Closes #10193 (do Invoke-DbaDbShrink) Co-authored-by: Andreas Jordan <andreasjordan@users.noreply.github.com>
1 parent 97d03be commit 94e0708

2 files changed

Lines changed: 63 additions & 2 deletions

File tree

public/Invoke-DbaDbShrink.ps1

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ function Invoke-DbaDbShrink {
5858
Sets the command timeout in minutes for the shrink operation. Defaults to 0 (infinite timeout).
5959
Large database shrinks can take hours to complete, so the default allows operations to run without timing out.
6060
61+
.PARAMETER WaitAtLowPriority
62+
Instructs DBCC SHRINKFILE to wait at low priority for schema modification locks, minimizing blocking of other sessions.
63+
Requires SQL Server 2022 (version 16) or later. When a lock cannot be obtained, SQL Server will abort the operation
64+
after approximately one minute and log error 49516. The AbortAfterWait parameter controls what is aborted.
65+
66+
.PARAMETER AbortAfterWait
67+
Specifies what to abort when the WAIT_AT_LOW_PRIORITY wait period expires. Valid values are Self and Blockers.
68+
Self aborts the shrink operation itself. Blockers kills the user sessions that block the lock acquisition.
69+
Defaults to Self. Only applies when WaitAtLowPriority is specified.
70+
6171
.PARAMETER LogsOnly
6272
Deprecated. Use FileType instead.
6373
@@ -165,6 +175,11 @@ function Invoke-DbaDbShrink {
165175
166176
Shrinks all databases coming from a pre-filtered list via Get-DbaDatabase
167177
178+
.EXAMPLE
179+
PS C:\> Invoke-DbaDbShrink -SqlInstance sql2022 -Database AdventureWorks2014 -WaitAtLowPriority -AbortAfterWait Blockers
180+
181+
Shrinks AdventureWorks2014 using WAIT_AT_LOW_PRIORITY, killing blocking sessions if the wait expires. Requires SQL Server 2022+.
182+
168183
#>
169184
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
170185
param (
@@ -183,6 +198,9 @@ function Invoke-DbaDbShrink {
183198
[string]$FileType = 'All',
184199
[int64]$StepSize,
185200
[int]$StatementTimeout = 0,
201+
[switch]$WaitAtLowPriority,
202+
[ValidateSet('Self', 'Blockers')]
203+
[string]$AbortAfterWait = 'Self',
186204
[switch]$ExcludeIndexStats,
187205
[switch]$ExcludeUpdateUsage,
188206
[switch]$EnableException,
@@ -202,6 +220,11 @@ function Invoke-DbaDbShrink {
202220
}
203221
$StatementTimeoutSeconds = $StatementTimeout * 60
204222

223+
$walp = ""
224+
if ($WaitAtLowPriority) {
225+
$walp = " WITH WAIT_AT_LOW_PRIORITY (ABORT_AFTER_WAIT = $($AbortAfterWait.ToUpper()))"
226+
}
227+
205228
$sql = 'SELECT
206229
AVG(avg_fragmentation_in_percent) AS [avg_fragmentation_in_percent]
207230
, MAX(avg_fragmentation_in_percent) AS [max_fragmentation_in_percent]
@@ -255,6 +278,10 @@ function Invoke-DbaDbShrink {
255278
continue
256279
}
257280

281+
if ($WaitAtLowPriority -and $instance.VersionMajor -lt 16) {
282+
Stop-Function -Message "WAIT_AT_LOW_PRIORITY for DBCC SHRINKFILE requires SQL Server 2022 (version 16) or later. $instance is running version $($instance.VersionMajor)." -Target $instance -Continue
283+
}
284+
258285
$files = @()
259286
if ($FileType -in ('Log', 'All')) {
260287
$files += $db.LogFiles
@@ -279,6 +306,8 @@ function Invoke-DbaDbShrink {
279306
Write-Message -Level Verbose -Message "Target Freespace: $($desiredSpaceAvailableKB)"
280307
Write-Message -Level Verbose -Message "Target FileSize: $($desiredFileSizeKB)"
281308

309+
$escapedFileName = $file.Name.Replace("'", "''")
310+
282311
if ($spaceAvailableKB -le $desiredSpaceAvailableKB) {
283312
Write-Message -Level Warning -Message "File size of ($startingSizeKB) is less than or equal to the desired outcome ($desiredFileSizeKB) for $($file.Name)"
284313
} else {
@@ -313,7 +342,14 @@ function Invoke-DbaDbShrink {
313342
$shrinkSizeKB = $desiredFileSizeKB
314343
}
315344
Write-Message -Level Verbose -Message ('Shrinking {0} to {1}' -f $file.Name, $shrinkSizeKB)
316-
$file.Shrink($shrinkSizeKB.Megabyte, $ShrinkMethod)
345+
$targetMB = [int]$shrinkSizeKB.Megabyte
346+
$shrinkSqlArgs = switch ($ShrinkMethod) {
347+
'EmptyFile' { "N'$escapedFileName', EMPTYFILE" }
348+
'NoTruncate' { "N'$escapedFileName', $targetMB, NOTRUNCATE" }
349+
'TruncateOnly' { "N'$escapedFileName', $targetMB, TRUNCATEONLY" }
350+
default { "N'$escapedFileName', $targetMB" }
351+
}
352+
$null = $instance.Query("DBCC SHRINKFILE ($shrinkSqlArgs)$walp", $db.name)
317353
$file.Refresh()
318354

319355
if ($startingSizeKB -eq ($file.Size * 1024)) {
@@ -322,7 +358,14 @@ function Invoke-DbaDbShrink {
322358
}
323359
}
324360
} else {
325-
$file.Shrink(($desiredFileSizeKB.Megabyte), $ShrinkMethod)
361+
$targetMB = [int]$desiredFileSizeKB.Megabyte
362+
$shrinkSqlArgs = switch ($ShrinkMethod) {
363+
'EmptyFile' { "N'$escapedFileName', EMPTYFILE" }
364+
'NoTruncate' { "N'$escapedFileName', $targetMB, NOTRUNCATE" }
365+
'TruncateOnly' { "N'$escapedFileName', $targetMB, TRUNCATEONLY" }
366+
default { "N'$escapedFileName', $targetMB" }
367+
}
368+
$null = $instance.Query("DBCC SHRINKFILE ($shrinkSqlArgs)$walp", $db.name)
326369
$file.Refresh()
327370
}
328371
$success = $true

tests/Invoke-DbaDbShrink.Tests.ps1

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ Describe $CommandName -Tag UnitTests {
2121
"FileType",
2222
"StepSize",
2323
"StatementTimeout",
24+
"WaitAtLowPriority",
25+
"AbortAfterWait",
2426
"ExcludeIndexStats",
2527
"ExcludeUpdateUsage",
2628
"EnableException",
@@ -155,5 +157,21 @@ Describe $CommandName -Tag IntegrationTests {
155157
$db.FileGroups[0].Files[0].Size | Should -BeLessThan $oldDataSize
156158
$db.LogFiles[0].Size | Should -Be $oldLogSize
157159
}
160+
161+
It "Shrinks with WaitAtLowPriority on SQL Server 2022+" -Skip:($server.VersionMajor -lt 16) {
162+
$result = Invoke-DbaDbShrink $server -Database $db.Name -FileType Data -WaitAtLowPriority -AbortAfterWait Self
163+
$result.Database | Should -Be $db.Name
164+
$result.File | Should -Be $db.Name
165+
$result.Success | Should -Be $true
166+
$db.Refresh()
167+
$db.RecalculateSpaceUsage()
168+
$db.FileGroups[0].Files[0].Refresh()
169+
$db.FileGroups[0].Files[0].Size | Should -BeLessThan $oldDataSize
170+
}
171+
172+
It "Returns an error when WaitAtLowPriority is used on SQL Server older than 2022" -Skip:($server.VersionMajor -ge 16) {
173+
$result = Invoke-DbaDbShrink $server -Database $db.Name -FileType Data -WaitAtLowPriority -WarningVariable warnings 3>&1
174+
$warnings | Should -Match "SQL Server 2022"
175+
}
158176
}
159177
}

0 commit comments

Comments
 (0)