Question

We have recently cleared out old data from our production database. The database is 3 TB with 1.4 TB empty this however poses a problem on the development and QA instance as we are not utilizing space by having 6 - 8 databases with 1.4 TB empty space especially with the space constraints we have on development. I want to setup a job to shrink the development databases using the below code

EXEC sp_MSForEachDB ' USE [?]; DBCC SHRINKFILE  (''?'' , 10)' 

EXEC sp_MSForEachDB ' USE [?]; DBCC SHRINKFILE (''?'' , 0, TRUNCATEONLY)' 

I am getting the following error, "Could not locate file 'Test' for database 'Test' in sys.database_files. The file either does not exist, or was dropped." The database has multiple data files. How could i improve my code to accommodate the multiple data files.

Was it helpful?

Solution

@sp_BlitzErik has correctly identified the problem, but I'd propose a different solution: use a one-time script that creates your SHRINKFILE statements, check them for sanity, then run them manually or put them into your agent job:

SELECT  dbname = DB_NAME(),
        file_name = name, 
        TotalMB = CONVERT(decimal(12,1),size/128.0),
        UsedMB = CONVERT(decimal(12,1),FILEPROPERTY(name,'SpaceUsed')/128.0),
        FreeMB = CONVERT(decimal(12,1),(size - FILEPROPERTY(name,'SpaceUsed'))/128.0),
        Command = CONCAT('USE ', DB_NAME(), '; DBCC SHRINKFILE (name = ',
              [name], ', size = ', 
              convert(int,round(1.15 * FILEPROPERTY(name,'SpaceUsed')/128,-1)), 'MB)')
 FROM sys.database_files WITH (NOLOCK)
 WHERE type_desc = 'ROWS'
 ORDER BY file_id;

Run this once from each database, it should return the total and used size for each data file (it skips log files, you can shrink those instantly by hand afterwards), and an example SHRINKFILE statement that gives you a target of 15% free space in the file, calculated from the current used space:

USE myDB; DBCC SHRINKFILE (name = myDBData, size = 148910MB)

You will need to check the results for sanity, if the file already has less than 15% free space, then the SHRINKFILE statement will specify a larger size than it currently has, so skip it (its already small enough).

After you've shrunk all the data files, pick a target size for each log file (I typically use 10-25% of the data file size), and shrink those by hand. This may depend on the recovery model, and also by how much activity these dbs get in that environment.

OTHER TIPS

The question mark will evaluate to the database name, not the file name you're trying to shrink.

For instance:

EXEC master.sys.sp_MSforeachdb ' USE [?]; PRINT N''?''; '; 

Will return (on my server)

master
tempdb
model
msdb
SUPERUSER
StackOverflow
StackOverflow_CS
Crap
DBAtools
StackOverflow2010
SUPERUSER_CX
ಠ_ಠ
StackOverflow2010ಠ_ಠ

DBCC SHRINKFILE doesn't take that as an argument:

DBCC SHRINKFILE (
{ file_name | file_id }
{ [ , EMPTYFILE ]
| [ [ , target_size ] [ , { NOTRUNCATE | TRUNCATEONLY } ] ]
} ) [ WITH NO_INFOMSGS ]

If you only have a .mdf and a .ldf, you can (likely, though not definitely) replace your code with:

EXEC sp_MSForEachDB ' USE [?]; DBCC SHRINKFILE  (1 , 10)' 

EXEC sp_MSForEachDB ' USE [?]; DBCC SHRINKFILE (2 , 0, TRUNCATEONLY)' 

More verbose code to look up file ids, etc. is left as an exercise to the reader.

If you want to just shrink the whole thing, use DBCC SHRINKDATABASE instead. That takes a database name, and will work with your original code.

DBCC SHRINKDATABASE ( database_name | database_id | 0
[ , target_percent ]
[ , { NOTRUNCATE | TRUNCATEONLY } ] ) [ WITH NO_INFOMSGS ]

Of course, this can cause all sorts of problems, and I wouldn't wanna do it.

@BradC this is how i adapted your suggested code

CREATE TABLE #ShrinkFile
(
DBName sysname,
File_Name sysname,
TotalMB decimal (18,2),
UsedMB decimal (18,2),
FreeMB decimal (18,2),
Command nvarchar(MAX)
) 
EXEC master.sys.sp_MSforeachdb ' USE [?]; 
    Insert Into #ShrinkFile (DBName, File_Name, TotalMB, UsedMB, FreeMB, 
Command)
    SELECT  dbname = DB_NAME(),
    file_name = name, 
    TotalMB = CONVERT(decimal(12,1),size/128.0),
    UsedMB = CONVERT(decimal(12,1),FILEPROPERTY(name,''SpaceUsed'')/128.0),
    FreeMB = CONVERT(decimal(12,1),(size - 
    FILEPROPERTY(name,''SpaceUsed''))/128.0),
    Command = CONCAT(''USE '', DB_NAME(), ''; DBCC SHRINKFILE (name = '',
          [name], '', size = '', 
          convert(int,round(1.15 * 
FILEPROPERTY(name,''SpaceUsed'')/128,-1)), ''MB)'')
FROM sys.database_files WITH (NOLOCK)
WHERE type_desc = ''ROWS''
ORDER BY file_id;'

IF EXISTS (SELECT * FROM  #ShrinkFile WHERE FreeMB > 1000)
BEGIN
   DECLARE @SQLText nvarchar(max)
   DECLARE Shrink_cursor CURSOR FOR 
   SELECT DISTINCT Command FROM #ShrinkFile
   WHERE FreeMB > 1000

   OPEN Shrink_cursor  
   FETCH NEXT FROM Shrink_cursor INTO @SQLText  

   WHILE @@FETCH_STATUS = 0  
   BEGIN  
        EXEC (@SQLText)
        FETCH NEXT FROM Shrink_cursor INTO @SQLText 
   END 

 CLOSE Shrink_cursor  
 DEALLOCATE Shrink_cursor 
 END

 DROP TABLE #ShrinkFile
Licensed under: CC-BY-SA with attribution
Not affiliated with dba.stackexchange
scroll top