Fix_VLFs.sql 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. -- 2011-05-24 Pedro Lopes (Microsoft) [email protected] (http://aka.ms/sqlinsights)
  2. --
  3. -- 2012-03-25 Added SQL 2012 support
  4. -- 2012-09-19 Simplified logic
  5. -- 2012-09-20 Changed grow settings if not SQL Server 2012
  6. --
  7. -- Generates the sql statements to preemtively fix VLF issues in all DBs within the server, based on the transaction log current size.
  8. --
  9. SET NOCOUNT ON;
  10. DECLARE @query VARCHAR(1000), @dbname VARCHAR(255), @count int, @usedlogsize bigint, @logsize bigint
  11. DECLARE @sqlcmd NVARCHAR(1000), @sqlparam NVARCHAR(100), @filename VARCHAR(255), @i int, @recmodel NVARCHAR(128)
  12. DECLARE @potsize int, @n_iter int, @n_iter_final int, @initgrow int, @n_init_iter int, @bckpath NVARCHAR(255)
  13. DECLARE @majorver smallint, @minorver smallint, @build smallint
  14. CREATE TABLE #loginfo (dbname varchar(100), num_of_rows int, used_logsize_MB DECIMAL(20,1))
  15. DECLARE @tblvlf TABLE (dbname varchar(100),
  16. Actual_log_size_MB DECIMAL(20,1),
  17. Potential_log_size_MB DECIMAL(20,1),
  18. Actual_VLFs int,
  19. Potential_VLFs int,
  20. Growth_iterations int,
  21. Log_Initial_size_MB DECIMAL(20,1),
  22. File_autogrow_MB DECIMAL(20,1))
  23. SELECT TOP 1 @bckpath = REVERSE(RIGHT(REVERSE(physical_device_name), LEN(physical_device_name)-CHARINDEX('\',REVERSE(physical_device_name),0))) FROM msdb.dbo.backupmediafamily WHERE device_type = 2
  24. SELECT @majorver = (@@microsoftversion / 0x1000000) & 0xff, @minorver = (@@microsoftversion / 0x10000) & 0xff, @build = @@microsoftversion & 0xffff
  25. --DECLARE csr CURSOR FAST_FORWARD FOR SELECT name FROM master..sysdatabases WHERE dbid > 4 AND DATABASEPROPERTYEX(name,'status') = 'ONLINE' AND DATABASEPROPERTYEX(name,'Updateability') = 'READ_WRITE' AND name <> 'tempdb' AND name <> 'ReportServerTempDB'
  26. DECLARE csr CURSOR FAST_FORWARD FOR SELECT name FROM master.sys.databases WHERE is_read_only = 0 AND state = 0 AND database_id <> 2;
  27. OPEN csr
  28. FETCH NEXT FROM csr INTO @dbname
  29. WHILE (@@FETCH_STATUS <> -1)
  30. BEGIN
  31. CREATE TABLE #log_info (recoveryunitid int NULL,
  32. fileid tinyint,
  33. file_size bigint,
  34. start_offset bigint,
  35. FSeqNo int,
  36. [status] tinyint,
  37. parity tinyint,
  38. create_lsn numeric(25,0))
  39. SET @query = 'DBCC LOGINFO (' + '''' + @dbname + ''') WITH NO_INFOMSGS'
  40. IF @majorver < 11
  41. BEGIN
  42. INSERT INTO #log_info (fileid, file_size, start_offset, FSeqNo, [status], parity, create_lsn)
  43. EXEC (@query)
  44. END
  45. ELSE
  46. BEGIN
  47. INSERT INTO #log_info (recoveryunitid, fileid, file_size, start_offset, FSeqNo, [status], parity, create_lsn)
  48. EXEC (@query)
  49. END
  50. SET @count = @@ROWCOUNT
  51. SET @usedlogsize = (SELECT (MIN(l.start_offset) + SUM(CASE WHEN l.status <> 0 THEN l.file_size ELSE 0 END))/1024.00/1024.00 FROM #log_info l)
  52. DROP TABLE #log_info;
  53. INSERT #loginfo
  54. VALUES(@dbname, @count, @usedlogsize);
  55. FETCH NEXT FROM csr INTO @dbname
  56. END
  57. CLOSE csr
  58. DEALLOCATE csr
  59. PRINT '/* Generated on ' + CONVERT (VARCHAR, GETDATE()) + ' in ' + @@SERVERNAME + ' */' + CHAR(10)
  60. DECLARE cshrk CURSOR FAST_FORWARD FOR SELECT dbname, num_of_rows FROM #loginfo
  61. WHERE num_of_rows >= 50 --My rule of thumb is 50 VLFs. Your mileage may vary.
  62. ORDER BY dbname
  63. OPEN cshrk
  64. FETCH NEXT FROM cshrk INTO @dbname, @count
  65. WHILE (@@FETCH_STATUS <> -1)
  66. BEGIN
  67. SET @sqlcmd = 'SELECT @nameout = name, @logsizeout = (CAST(size AS BIGINT)*8)/1024 FROM [' + @dbname + '].dbo.sysfiles WHERE (64 & status) = 64'
  68. SET @sqlparam = '@nameout NVARCHAR(100) OUTPUT, @logsizeout bigint OUTPUT'
  69. EXEC sp_executesql @sqlcmd, @sqlparam, @nameout = @filename OUTPUT, @logsizeout = @logsize OUTPUT;
  70. PRINT '---------------------------------------------------------------------------------------------------------- '
  71. PRINT CHAR(13) + 'USE ' + QUOTENAME(@dbname) + ';'
  72. PRINT 'DBCC SHRINKFILE (N''' + @filename + ''', 1, TRUNCATEONLY);'
  73. PRINT '--'
  74. PRINT '-- CHECK: if the tlog file has shrunk with the following query:'
  75. PRINT 'SELECT name, (size*8)/1024 AS log_MB FROM [' + @dbname + '].dbo.sysfiles WHERE (64 & status) = 64'
  76. PRINT '--'
  77. SET @recmodel = CONVERT(NVARCHAR, DATABASEPROPERTYEX(@dbname,'Recovery'))
  78. IF @recmodel <> 'SIMPLE'
  79. AND SERVERPROPERTY('EngineEdition') <> 8 -- This cannot be applied on Managed Instance
  80. BEGIN
  81. PRINT '-- If the log has not shrunk, you must backup the transaction log next.'
  82. PRINT '-- Repeat the backup and shrink process alternatively until you get the desired log size (about 1MB).'
  83. PRINT '--'
  84. PRINT '-- METHOD: Backup -> Shrink (repeat the backup and shrink process until the log has shrunk):'
  85. PRINT '--'
  86. PRINT '-- Create example logical backup device.'
  87. PRINT 'USE master;' + CHAR(13) + 'EXEC sp_addumpdevice ''disk'', ''BckLog'', ''' + @bckpath + '\example_bck.trn'';'
  88. PRINT 'USE ' + QUOTENAME(@dbname) + ';'
  89. PRINT '-- Backup Log'
  90. PRINT 'BACKUP LOG ' + QUOTENAME(@dbname) + ' TO BckLog;'
  91. PRINT '-- Shrink'
  92. PRINT 'DBCC SHRINKFILE (N''' + @filename + ''', 1);'
  93. PRINT '--'
  94. PRINT '-- METHOD: Alter recovery model -> Shrink:'
  95. PRINT '-- NOTE: Because the database is in ' + @recmodel + ' recovery model, one alternative is to set it to SIMPLE to truncate the log, shrink it, and reset it to ' + @recmodel + '.'
  96. PRINT '-- NOTE2: This method of setting the recovery model to SIMPLE and back again WILL BREAK log chaining, and thus any log shipping or mirroring.'
  97. PRINT 'USE [master]; ' + CHAR(13) + 'ALTER DATABASE ' + QUOTENAME(@dbname) + ' SET RECOVERY SIMPLE;'
  98. PRINT 'USE ' + QUOTENAME(@dbname) + ';' + CHAR(13) + 'DBCC SHRINKFILE (N''' + @filename + ''', 1);'
  99. PRINT 'USE [master]; ' + CHAR(13) + 'ALTER DATABASE ' + QUOTENAME(@dbname) + ' SET RECOVERY ' + @recmodel + ';'
  100. PRINT '--'
  101. PRINT '-- CHECK: if the tlog file has shrunk with the following query:'
  102. PRINT 'SELECT name, (size*8)/1024 AS log_MB FROM [' + @dbname + '].dbo.sysfiles WHERE (64 & status) = 64'
  103. END
  104. ELSE
  105. BEGIN
  106. PRINT '-- If not, then proceed to the next step (it may be necessary to execute multiple times):'
  107. PRINT 'DBCC SHRINKFILE (N''' + @filename + ''', 1);'
  108. PRINT '-- CHECK: if the tlog file has shrunk with the following query:'
  109. PRINT 'SELECT name, (size*8)/1024 AS log_MB FROM [' + @dbname + '].dbo.sysfiles WHERE (64 & status) = 64'
  110. END
  111. -- We are growing in MB instead of GB because of known issue prior to SQL 2012.
  112. -- More detail here: http://www.sqlskills.com/BLOGS/PAUL/post/Bug-log-file-growth-broken-for-multiples-of-4GB.aspx
  113. -- and http://connect.microsoft.com/SQLServer/feedback/details/481594/log-growth-not-working-properly-with-specific-growth-sizes-vlfs-also-not-created-appropriately
  114. -- or https://connect.microsoft.com/SQLServer/feedback/details/357502/transaction-log-file-size-will-not-grow-exactly-4gb-when-filegrowth-4gb
  115. IF @majorver >= 11
  116. BEGIN
  117. SET @n_iter = (SELECT CASE WHEN @logsize <= 64 THEN 1
  118. WHEN @logsize > 64 AND @logsize < 256 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/256, 0)
  119. WHEN @logsize >= 256 AND @logsize < 1024 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/512, 0)
  120. WHEN @logsize >= 1024 AND @logsize < 4096 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/1024, 0)
  121. WHEN @logsize >= 4096 AND @logsize < 8192 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/2048, 0)
  122. WHEN @logsize >= 8192 AND @logsize < 16384 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/4096, 0)
  123. WHEN @logsize >= 16384 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/8192, 0)
  124. END)
  125. SET @potsize = (SELECT CASE WHEN @logsize <= 64 THEN 1*64
  126. WHEN @logsize > 64 AND @logsize < 256 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/256, 0)*256
  127. WHEN @logsize >= 256 AND @logsize < 1024 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/512, 0)*512
  128. WHEN @logsize >= 1024 AND @logsize < 4096 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/1024, 0)*1024
  129. WHEN @logsize >= 4096 AND @logsize < 8192 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/2048, 0)*2048
  130. WHEN @logsize >= 8192 AND @logsize < 16384 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/4096, 0)*4096
  131. WHEN @logsize >= 16384 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/8192, 0)*8192
  132. END)
  133. END
  134. ELSE
  135. BEGIN
  136. SET @n_iter = (SELECT CASE WHEN @logsize <= 64 THEN 1
  137. WHEN @logsize > 64 AND @logsize < 256 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/256, 0)
  138. WHEN @logsize >= 256 AND @logsize < 1024 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/512, 0)
  139. WHEN @logsize >= 1024 AND @logsize < 4096 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/1024, 0)
  140. WHEN @logsize >= 4096 AND @logsize < 8192 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/2048, 0)
  141. WHEN @logsize >= 8192 AND @logsize < 16384 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/4000, 0)
  142. WHEN @logsize >= 16384 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/8000, 0)
  143. END)
  144. SET @potsize = (SELECT CASE WHEN @logsize <= 64 THEN 1*64
  145. WHEN @logsize > 64 AND @logsize < 256 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/256, 0)*256
  146. WHEN @logsize >= 256 AND @logsize < 1024 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/512, 0)*512
  147. WHEN @logsize >= 1024 AND @logsize < 4096 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/1024, 0)*1024
  148. WHEN @logsize >= 4096 AND @logsize < 8192 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/2048, 0)*2048
  149. WHEN @logsize >= 8192 AND @logsize < 16384 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/4000, 0)*4000
  150. WHEN @logsize >= 16384 THEN ROUND(CONVERT(FLOAT, ROUND(@logsize, -2))/8000, 0)*8000
  151. END)
  152. END
  153. -- If the proposed log size is smaller than current log, and also smaller than 4GB,
  154. -- and there is less than 512MB of diff between the current size and proposed size, add 1 grow.
  155. SET @n_iter_final = @n_iter
  156. IF @logsize > @potsize AND @potsize <= 4096 AND ABS(@logsize - @potsize) < 512
  157. BEGIN
  158. SET @n_iter_final = @n_iter + 1
  159. END
  160. -- If the proposed log size is larger than current log, and also larger than 50GB,
  161. -- and there is less than 1GB of diff between the current size and proposed size, take 1 grow.
  162. ELSE IF @logsize < @potsize AND @potsize <= 51200 AND ABS(@logsize - @potsize) > 1024
  163. BEGIN
  164. SET @n_iter_final = @n_iter - 1
  165. END
  166. IF @potsize = 0
  167. BEGIN
  168. SET @potsize = 64
  169. END
  170. IF @n_iter = 0
  171. BEGIN
  172. SET @n_iter = 1
  173. END
  174. SET @potsize = (SELECT CASE WHEN @n_iter < @n_iter_final THEN @potsize + (@potsize/@n_iter)
  175. WHEN @n_iter > @n_iter_final THEN @potsize - (@potsize/@n_iter)
  176. ELSE @potsize END)
  177. SET @n_init_iter = @n_iter_final
  178. IF @potsize >= 8192
  179. BEGIN
  180. SET @initgrow = @potsize/@n_iter_final
  181. END
  182. IF @potsize >= 64 AND @potsize <= 512
  183. BEGIN
  184. SET @n_init_iter = 1
  185. SET @initgrow = 512
  186. END
  187. IF @potsize > 512 AND @potsize <= 1024
  188. BEGIN
  189. SET @n_init_iter = 1
  190. SET @initgrow = 1023
  191. END
  192. IF @potsize > 1024 AND @potsize < 8192
  193. BEGIN
  194. SET @n_init_iter = 1
  195. SET @initgrow = @potsize
  196. END
  197. INSERT INTO @tblvlf
  198. SELECT @dbname, @logsize, @potsize, @count,
  199. CASE WHEN @potsize <= 64 THEN (@potsize/(@potsize/@n_init_iter))*4
  200. WHEN @potsize > 64 AND @potsize < 1024 THEN (@potsize/(@potsize/@n_init_iter))*8
  201. WHEN @potsize >= 1024 THEN (@potsize/(@potsize/@n_init_iter))*16
  202. END,
  203. @n_init_iter, @initgrow, CASE WHEN (@potsize/@n_iter_final) <= 1024 THEN (@potsize/@n_iter_final) ELSE 1024 END
  204. SET @i = 0
  205. WHILE @i <= @n_init_iter
  206. BEGIN
  207. IF @i = 1
  208. BEGIN
  209. --Log Autogrow should not be above 1GB
  210. PRINT CHAR(13) + '-- Now for the log file growth:'
  211. PRINT 'ALTER DATABASE [' + @dbname + '] MODIFY FILE ( NAME = N''' + @filename + ''', SIZE = ' + CONVERT(VARCHAR, @initgrow) + 'MB , FILEGROWTH = ' + CASE WHEN (@potsize/@n_iter_final) <= 1024 THEN CONVERT(VARCHAR, (@potsize/@n_iter_final)) ELSE '1024' END + 'MB );'
  212. END
  213. IF @i > 1
  214. BEGIN
  215. PRINT 'ALTER DATABASE [' + @dbname + '] MODIFY FILE ( NAME = N''' + @filename + ''', SIZE = ' + CONVERT(VARCHAR, @initgrow*@i)+ 'MB );'
  216. END
  217. SET @i = @i + 1
  218. CONTINUE
  219. END
  220. FETCH NEXT FROM cshrk INTO @dbname, @count
  221. END
  222. CLOSE cshrk
  223. DEALLOCATE cshrk;
  224. DROP TABLE #loginfo;
  225. SELECT dbname AS [Database_Name], Actual_log_size_MB, Potential_log_size_MB, Actual_VLFs,
  226. Potential_VLFs, Growth_iterations, Log_Initial_size_MB, File_autogrow_MB
  227. FROM @tblvlf;
  228. GO