Review某個SQLServer的生產環境的errolog的時候,無意中發現這麼一條日誌,意思是過多的VLF文件,會影響數據庫的啓動速度和日誌備份效率。
Database *** has more than 10000 virtual log files which is excessive. Too many virtual log files can cause long startup and backup times.Consider shrinking the log and using a different growth increment to reduce the number of virtual log files.
由於這個庫已經存在很久了,猜測可能是當時設置的SQLServer的事務日誌文件增長設置不合理導致的,雖然問題不大,這個情況還是簡單總結一下處理方式。
VLF(虛擬日誌文件)的副作用
事務日誌中VLF會有一些負面影響,在數據庫恢復過程的初始階段,SQL Server 會遍歷所有事務日誌文件中的所有 VLF,並生成這些 VLF 的列表。 此過程可能需要很長時間,具體取決於特定數據庫中存在的 VLF 數量。 VLF 越多,過程越長。
如果遇到頻繁的事務日誌自動增長或小增量手動增長,數據庫最終可能會出現大量 VLF。 當 VLF 數量達到數十萬的範圍時,你可能會遇到以下部分或大部分症狀:
如果遇到頻繁的事務日誌自動增長或小增量手動增長,數據庫最終可能會出現大量 VLF。 當 VLF 數量達到數十萬的範圍時,你可能會遇到以下部分或大部分症狀:
在 SQL Server 啓動期間,一個或多個數據庫需要很長時間才能完成恢復。
還原數據庫需要很長時間才能完成。
嘗試附加數據庫需要很長時間才能完成。
嘗試設置數據庫鏡像時,遇到錯誤消息 1413、1443 和 1479,表示超時。
嘗試還原數據庫時,遇到與內存相關的錯誤,如 701。
事務複製或變更數據捕獲可能會出現明顯延遲。
事務日誌和VLF(虛擬日誌文件)的關係
那麼,事務日誌和VLF文件的個數是怎麼樣的一種關係呢?或者説VLF(虛擬日誌文件的增長模式)是怎麼樣的?
1,在 SQL Server 2014 (12.x) 及更高版本中,如果下一次增長少於當前日誌物理大小的 1/8,則創建 1 個 VLF,補償此增長大小。
2,如果下一次增長超過當前日誌大小的 1/8,則使用 pre-2014 方法,即:
2.1,如果增長少於 64 MB,則創建 4 個 VLF,補償此增長大小(例如,增長 1 MB,創建 4 個 256 KB 的 VLF)。
在 Azure SQL 數據庫中,從 SQL Server 2022 (16.x)(所有版本)開始,邏輯略有不同。 如果增長小於或等於 64 MB,則數據庫引擎只創建一個 VLF 來補償此增長大小。
2.2,如果增長來自 64 MB(至 1 GB),則創建 8 個 VLF,補償此增長大小(例如,增長 512 MB,創建 8 個 64 MB 的 VLF)。
2.3,如果增長大於 1 GB,則創建 16 個 VLF,補償此增長大小(例如,增長 8 GB,創建 16 個 512 MB 的 VLF)。
從2.1中可以看到,如果設置的日誌增長值過小,會導致生成多個較小的VLF。那麼又如何修復這種情況呢?
VLF(虛擬日誌文件)過多的修復方案
如下是一個完整的測試腳本以及驗證方案。
--建庫腳本,刻意將日誌文件的增長設置為1MB(FILEGROWTH = 1024KB)
CREATE DATABASE [DB01]
CONTAINMENT = NONE
ON PRIMARY
( NAME = N'DB01', FILENAME = N'D:\MSSQL\DB01.mdf' , SIZE = 512MB , MAXSIZE = UNLIMITED, FILEGROWTH = 65536KB )
LOG ON
( NAME = N'DB01_log', FILENAME = N'D:\MSSQL\DB01_log.ldf' , SIZE = 64MB , MAXSIZE = 2048GB , FILEGROWTH = 1024KB )
WITH CATALOG_COLLATION = DATABASE_DEFAULT, LEDGER = OFF
GO
--新建的數據庫默認是完整恢復模式,建庫之後做一次完整的備份,否則數據庫還是簡單恢復模式
backup database DB01 to disk = 'D:\MSSQL\DB01.bak';
-- 查看 DB 增長信息
use DB01;
go
SELECT
name,
type_desc ,
size/128.0 AS current_size,
-- growth: 8kb page number
CASE is_percent_growth
WHEN 1 THEN CAST(growth AS VARCHAR) + '%'
ELSE CAST(growth*8/1024.0 AS VARCHAR) + ' MB'
END AS 'growth',
physical_name
FROM sys.database_files;
--創建一個測試表
create table t1
(
c1 int identity(1,1),
c2 varchar(50),
c3 varchar(50),
c4 varchar(50),
c5 varchar(50),
c6 varchar(50),
c7 varchar(50),
c8 datetime2,
constraint pk_t1_c1 primary key(c1)
);
--往測試表寫入數據
insert into t1
select newid(),newid(),newid(),newid(),newid(),newid(),GETDATE() from sys.objects a , sys.objects b, sys.objects c;
--再多執行幾次這個語句,生成足夠多的事務日誌
insert into t1
select newid(),newid(),newid(),newid(),newid(),newid(),GETDATE() from t1;
-- sqlserver 事務日誌和包含的虛擬日誌文件個數
SELECT db.name, count(dbl.database_id) as Total_VLF_count, convert(decimal (10,2), avg(dbl.vlf_size_mb)) as Avg_VLF_Size_MB
FROM sys.databases db
CROSS APPLY sys.dm_db_log_info(db.database_id) dbl
where name = 'DB01'
GROUP BY db.name
ORDER BY Total_VLF_count DESC;
--查看數據文件使用比例
SELECT file_id, name,type_desc,
CAST(FILEPROPERTY(name, 'SpaceUsed') AS decimal(19,4)) * 8 / 1024. AS space_used_mb,
CAST(size/128.0 - CAST(FILEPROPERTY(name, 'SpaceUsed') AS int)/128.0 AS decimal(19,4)) AS space_unused_mb,
CAST(size AS decimal(19,4)) * 8 / 1024. AS space_allocated_mb,
CAST(max_size AS decimal(19,4)) * 8 / 1024. AS max_size_mb
FROM sys.database_files;
-- 事務日誌使用情況
DBCC SQLPERF(LOGSPACE);
-- 使用DBCC LOGINFO查看虛擬日誌文件(VLF)狀態,(0=空閒,2=活動)
DBCC LOGINFO;
-- 查看日誌截斷原因
SELECT
name AS '數據庫名稱',
log_reuse_wait_desc AS '日誌重用等待原因'
FROM sys.databases;
-- DB Size 查看
EXEC sp_helpdb 'DB01';
--修復包含大量 VLF 的數據庫
solution1:
1,備份事務日誌
backup log DB01 to disk = 'D:\MSSQL\db01.trn2';
2,手動收縮事務日誌文件,如下單位是MB
DBCC SHRINKFILE (N'DB01_log' , 64);
--這種方式收縮無效,需要收縮到指定大小才行
DBCC SHRINKFILE (N'DB01_log' , 0, TRUNCATEONLY);
******特別注意******
1,手動收縮的目的是消除已經分配給事務日誌文件的那些小的VLF
2,個人在SQLServer 2019標準版下實測,備份事務日誌後收縮,並不能一次就達到目的,需要執行2~3次備份+收縮操作才能將日誌收縮至最小,
按道理第一次備份玩日誌就可以截斷了(測試環境,備份後沒有繼續寫入),實際需要執行2~3次備份和收縮才行
3,使用以下 T-SQL 腳本在一個步驟中手動將文件增長到所需的大小,避免頻繁增長導致過多的VLF:
ALTER DATABASE DB01 MODIFY FILE (NAME='DB01_log', SIZE = 1024MB);
請注意:上述直接修改DB01_log的大小的時候,生成的VLF的個數,不受限於上述默認自動增長生成VLF個數的規則
solution2:
1,備份事務日誌
2,手動收縮事務日誌文件。
3,ALTER DATABASE DB01 MODIFY FILE (NAME = 'DB01_log', FILEGROWTH = 512MB);
refer:
https://learn.microsoft.com/zh-cn/sql/relational-databases/errors-events/mssqlserver-9017-database-engine-error?view=sql-server-ver16
https://learn.microsoft.com/zh-cn/sql/relational-databases/sql-server-transaction-log-architecture-and-management-guide?view=sql-server-ver16
https://learn.microsoft.com/zh-cn/sql/relational-databases/errors-events/mssqlserver-9017-database-engine-error?view=sql-server-ver16
https://learn.microsoft.com/zh-cn/sql/relational-databases/sql-server-transaction-log-architecture-and-management-guide?view=sql-server-ver16