在 SQL Server 2014 中,带有 TABLOCK 提示的 INSERT 语句可以在完全恢复模式下最少记录,为什么?

INSERT statement with TABLOCK hint can be minimum logged under full recovery mode in SQL Server 2014, why?

来自微软 Understanding Minimally Logged Operations

Minimally logged operations are available only if your database is in bulk-logged or simple recovery mode.

下面的代码解释了我所看到的。

use master;

IF DB_ID('RecoveryETL') IS NOT NULL
BEGIN
    ALTER DATABASE RecoveryETL SET SINGLE_USER WITH ROLLBACK IMMEDIATE 
    DROP DATABASE RecoveryETL;
END

--Create sample database for testing
CREATE DATABASE RecoveryETL 
ON PRIMARY
    (NAME = N'RecoveryETL_Date', FILENAME = N'C:\SQLDATA\RecoveryETL_Data.Mdf', SIZE = 1024MB, MAXSIZE = UNLIMITED, FILEGROWTH = 50MB)
LOG ON
    (NAME = N'RecoveryETL_Log', FILENAME = N'C:\SQLDATA\RecoveryETL_Log.LDF', SIZE = 100MB, MAXSIZE = 1024MB, FILEGROWTH = 25MB)
GO

--Set Recovery Mode to Full
ALTER DATABASE RecoveryETL SET RECOVERY FULL ;
GO

USE RecoveryETL;
GO

--Immediately perform a full backup otherwise database will remain in simple mode
BACKUP DATABASE RecoveryETL
TO DISK = 'C:\Database Backups\RecoveryETL_1.bak'
WITH RETAINDAYS = 90 
, FORMAT
, INIT
, MEDIANAME = 'RecoveryETL'
, NAME = 'RecoveryETL-Full Database Backup'
, COMPRESSION ;
GO

--Create a Testing Table
IF OBJECT_ID('dbo.HeapTable') IS NOT NULL 
DROP TABLE dbo.HeapTable

CREATE TABLE HeapTable
(
    Fld1 INT, 
    Fld2 INT, 
    FId3 INT,
    ModDate Datetime
)

--Insert Records W/O TABLOCK hint
INSERT INTO HeapTable (Fld1, Fld2, FId3, ModDate)
    SELECT TOP 10000 
        SalesOrderDetailID, SalesOrderDetailID + 100, SalesOrderDetailID + 99, GETDATE()
    FROM 
        AdventureWorks2012.Sales.SalesOrderDetail

--Is it minimum logged? No, more than 10,000 log records
SELECT * 
FROM sys.fn_dblog(null, null)
WHERE AllocUnitName LIKE '%HeapTable%' 

--Truncate logs
CHECKPOINT;

BACKUP LOG RecoveryETL
TO DISK = 'NULL'
WITH RETAINDAYS = 90
, NOINIT
, MEDIANAME = 'RecoveryETL'
, NAME = 'RecoveryETL-Log Backup'
, COMPRESSION ;
GO

--Make sure logs are gone
SELECT * 
FROM sys.fn_dblog(null, null)
WHERE AllocUnitName LIKE '%HeapTable%' 

--Insert records WITH TABLOCK hint
INSERT INTO HeapTable WITH(TABLOCK)(Fld1, Fld2, FId3, ModDate)
    SELECT TOP 10000 
        SalesOrderDetailID, SalesOrderDetailID + 100, SalesOrderDetailID + 99, GETDATE()
    FROM 
        AdventureWorks2012.Sales.SalesOrderDetail

--Minimum logged here!!!!!
SELECT * 
FROM sys.fn_dblog(null, null)
WHERE AllocUnitName LIKE '%HeapTable%' 

我还尝试使用具有最少日志记录的日志备份来恢复数据库时间点,正如预期的那样,我无法使数据库脱离恢复状态,我可以说它们确实是最小日志记录。官方文档不准确?

where I can say they are truly minimum logged. The official documentation is inaccurate?

不,你错了。

是的,INSERT 的完全记录方式发生了变化,但在 full recovery model 下,即使使用 TABLOCK,它们仍然是 f完全记录.

In SQL SERVER 2005-2008 INSERT under full recovery model 是逐行记录的,所以如果你在日志文件中做了 10000 行 INSERT 你看到至少 10000 LOP_INSERT_ROW 次操作(见我的第一张照片)

现在我在 SQL SERVER 2016 上做了同样的 INSERT,首先在 BULK_LOGGED 恢复模型下,然后在 FULL 下(你可以在我的第二张图片上看到结果) .在 BULK_LOGGED 下,您可以看到仅记录了位图设置,因此操作实际上是 minimally logged。但在 FULL 恢复模式下,情况有所不同。是的,您不再看到 10000 LOP_INSERT_ROW 行,但现在您可以看到 38 个 LOP_FORMAT_PAGE 操作,并且与此操作对应的日志行是 8276 字节,即 8Kb , 一个 page。这 38 页是在 INSERT 期间格式化的整页图像,甚至在 2008 年对 INDEX REBUILD 操作也是如此。所以操作仍然是 fully logged,进入日志的信息足以完全重构操作,而不仅仅是允许回滚,就像 minimally logged 操作一样。因此,即使有 TABLOCK 提示,INSERT 进入堆仍然是 fully logged 操作,但现在它甚至是 efficiently logged 操作。在 FULL 下插入后,我什至尝试备份日志,并且在备份日志完成之前,我能够使用 stop at 从该日志中恢复。