I'm testing minimal logging inserts in different scenarios and from what I've read `INSERT INTO SELECT` into a heap with a non clustered index using `TABLOCK` and SQL Server 2016+ should minimally log, however in my case when doing this I'm getting full logging. My database is in the simple recovery model and I successfully get minimally logged inserts on a heap with no indexes and `TABLOCK`.
I'm using an old backup of the Stack Overflow database to test on and have created a replicate of the Posts table with the following schema...
CREATE TABLE [dbo].[PostsDestination](
[Id] [int] NOT NULL,
[AcceptedAnswerId] [int] NULL,
[AnswerCount] [int] NULL,
[Body] [nvarchar](max) NOT NULL,
[ClosedDate] [datetime] NULL,
[CommentCount] [int] NULL,
[CommunityOwnedDate] [datetime] NULL,
[CreationDate] [datetime] NOT NULL,
[FavoriteCount] [int] NULL,
[LastActivityDate] [datetime] NOT NULL,
[LastEditDate] [datetime] NULL,
[LastEditorDisplayName] [nvarchar](40) NULL,
[LastEditorUserId] [int] NULL,
[OwnerUserId] [int] NULL,
[ParentId] [int] NULL,
[PostTypeId] [int] NOT NULL,
[Score] [int] NOT NULL,
[Tags] [nvarchar](150) NULL,
[Title] [nvarchar](250) NULL,
[ViewCount] [int] NOT NULL
)
CREATE NONCLUSTERED INDEX ndx_PostsDestination_Id ON PostsDestination(Id)
I then try to copy the posts table into this table...
INSERT INTO PostsDestination WITH(TABLOCK)
SELECT * FROM Posts ORDER BY Id
From looking at fn_dblog and the log file usage I can see I'm not getting minimal logging from this. I've read that versions before 2016 require trace flag 610 to minimally log to indexed tables, I've also tried setting this but still no joy.
I'm guessing I'm missing something here?
## More information
To add more info I'm using the following procedure that I've written to try to detect minimal logging, maybe I've got something here wrong...
/*
Example Usage...
EXEC sp_GetLogUseStats
@Sql = '
INSERT INTO PostsDestination
SELECT TOP 500000 * FROM Posts ORDER BY Id ',
@Schema = 'dbo',
@Table = 'PostsDestination',
@ClearData = 1
*/
CREATE PROCEDURE [dbo].[sp_GetLogUseStats]
(
@Sql NVARCHAR(400),
@Schema NVARCHAR(20),
@Table NVARCHAR(200),
@ClearData BIT = 0
)
AS
IF @ClearData = 1
BEGIN
TRUNCATE TABLE PostsDestination
END
/*Checkpoint to clear log (Assuming Simple/Bulk Recovery Model*/
CHECKPOINT
/*Snapshot of logsize before query*/
CREATE TABLE #BeforeLogUsed(
[Db] NVARCHAR(100),
LogSize NVARCHAR(30),
Used NVARCHAR(50),
Status INT
)
INSERT INTO #BeforeLogUsed
EXEC('DBCC SQLPERF(logspace)')
/*Run Query*/
EXECUTE sp_executesql @SQL
/*Snapshot of logsize after query*/
CREATE TABLE #AfterLLogUsed(
[Db] NVARCHAR(100),
LogSize NVARCHAR(30),
Used NVARCHAR(50),
Status INT
)
INSERT INTO #AfterLLogUsed
EXEC('DBCC SQLPERF(logspace)')
/*Return before and after log size*/
SELECT
CAST(#AfterLLogUsed.Used AS DECIMAL(12,4)) - CAST(#BeforeLogUsed.Used AS DECIMAL(12,4)) AS LogSpaceUsersByInsert
FROM
#BeforeLogUsed
LEFT JOIN #AfterLLogUsed ON #AfterLLogUsed.Db = #BeforeLogUsed.Db
WHERE
#BeforeLogUsed.Db = DB_NAME()
/*Get list of affected indexes from insert query*/
SELECT
@Schema + '.' + so.name + '.' + si.name AS IndexName
INTO
#IndexNames
FROM
sys.indexes si
JOIN sys.objects so ON si.[object_id] = so.[object_id]
WHERE
si.name IS NOT NULL
AND so.name = @Table
/*Insert Record For Heap*/
INSERT INTO #IndexNames VALUES(@Schema + '.' + @Table)
/*Get log recrod sizes for heap and/or any indexes*/
SELECT
AllocUnitName,
[operation],
AVG([log record length]) AvgLogLength,
SUM([log record length]) TotalLogLength,
COUNT(*) Count
INTO #LogBreakdown
FROM
fn_dblog(null, null) fn
INNER JOIN #IndexNames ON #IndexNames.IndexName = allocunitname
GROUP BY
[Operation], AllocUnitName
ORDER BY AllocUnitName, operation
SELECT * FROM #LogBreakdown
SELECT AllocUnitName, SUM(TotalLogLength) TotalLogRecordLength
FROM #LogBreakdown
GROUP BY AllocUnitName
Inserting into a heap with no indexes and TABLOCK using following code...
EXEC sp_GetLogUseStats
@Sql = '
INSERT INTO PostsDestination
SELECT * FROM Posts ORDER BY Id ',
@Schema = 'dbo',
@Table = 'PostsDestination',
@ClearData = 1
I get these results
[![enter image description here][1]][1]
At 0.0024mb log file growth, very small log record sizes and very few of them I'm happy that this is using minimal logging.
If I then create a non clustered index on id...
CREATE INDEX ndx_PostsDestination_Id ON PostsDestination(Id)
Then run my same insert again...
[![enter image description here][2]][2]
Not only am I not getting minimal logging on the non clustered index but I've also lost it on the heap. After doing some more tests it seems if I make ID clustered it does minimally log but from what I've read 2016+ should minimally log to a heap with non clustered index when tablock is used.
I've reported the behaviour to Microsoft on the [SQL Server UserVoice](https://feedback.azure.com/forums/908035-sql-server/suggestions/34407871-sql-server-2017-minimal-logging-not-behaving-as-d) and will update if I get a response. I've also written up the full details of the minimal log scenarios that I couldn't get to work at https://gavindraper.com/2018/05/29/SQL-Server-Minimal-Logging-Inserts/
[1]: https://i.stack.imgur.com/pvmcA.png
[2]: https://i.stack.imgur.com/k8G7D.png
[3]: https://feedback.azure.com/forums/908035-sql-server/suggestions/34407871-sql-server-2017-minimal-logging-not-behaving-as-do