Dlaczego CHECKDB odczytuje plik dziennika transakcji w bazie danych ze zoptymalizowaną pamięcią tabeli?

16

tl; dr : dlaczego CHECKDB odczytuje dziennik transakcji dla bazy danych użytkowników z tabelami zoptymalizowanymi pod kątem pamięci?


Wygląda na to, że CHECKDB odczytuje plik dziennika transakcji bazy danych użytkownika podczas sprawdzania jednej z moich baz danych - w szczególności bazy danych, która wykorzystuje tabele OLTP w pamięci.

CHECKDB dla tej bazy danych nadal kończy się w rozsądnym czasie, więc jestem głównie ciekawy tego zachowania; ale jest to zdecydowanie najdłuższy czas trwania CHECKDB ze wszystkich baz danych w tej instancji.

Patrząc na epicką historię Paula Randala „ CHECKDB pod każdym kątem: pełny opis wszystkich etapów CHECKDB ”, widzę, że CHECKDB sprzed SQL 2005 był używany do odczytu dziennika, aby uzyskać spójny widok bazy danych. Ale ponieważ jest to 2016 r., Używa wewnętrznej migawki bazy danych.

Jednak jednym z warunków wstępnych migawek jest:

Źródłowa baza danych nie może zawierać grupa plików MEMORY_OPTIMIZED_DATA

Moja baza danych użytkowników ma jedną z tych aplikacjami, więc wygląda na to, że migawki są poza tabelą.

Według dokumentów CHECKDB :

Jeśli nie można utworzyć migawki lub określono TABLOCK, DBCC CHECKDB nabywa blokady w celu uzyskania wymaganej spójności. W takim przypadku wymagana jest blokada wyłącznej bazy danych w celu wykonania kontroli alokacji, a blokady tabeli udostępnionej są wymagane do przeprowadzenia kontroli tabeli.

Okej, więc wykonujemy blokowanie baz danych i tabel zamiast migawek. Ale to wciąż nie wyjaśnia, dlaczego musi czytać dziennik transakcji. Co więc daje?

Poniżej przedstawiłem skrypt do odtworzenia scenariusza. Służy sys.dm_io_virtual_file_statsdo identyfikacji odczytanych plików dziennika.

Pamiętaj, że przez większość czasu czyta niewielką część dziennika (480 KB), ale czasami czyta znacznie więcej (48,2 MB). W moim scenariuszu produkcyjnym odczytuje większość pliku dziennika (~ 1,3 GB z pliku 2 GB) każdej nocy o północy, kiedy uruchamiamy CHECKDB.

Oto przykład wyników, które uzyskałem do tej pory ze skryptu:

collection_time            num_of_reads     num_of_bytes_read
2018-04-04 15:12:29.203    106              50545664

Albo to:

collection_time            num_of_reads     num_of_bytes_read
2018-04-04 15:25:14.227    1                491520

Jeśli zastąpię obiekty zoptymalizowane pod kątem pamięci zwykłymi tabelami, dane wyjściowe wyglądają następująco:

collection_time            num_of_reads     num_of_bytes_read
2018-04-04 15:21:03.207    0                0

Dlaczego CHECKDB czyta plik dziennika? A zwłaszcza dlaczego czasami odczytuje znacznie większą część pliku dziennika?

Oto rzeczywisty skrypt:

-- let's have a fresh DB
USE [master];

IF (DB_ID(N'LogFileRead_Test') IS NOT NULL) 
BEGIN
    ALTER DATABASE [LogFileRead_Test]
    SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
    DROP DATABASE [LogFileRead_Test];
END

GO
CREATE DATABASE [LogFileRead_Test]

GO
ALTER DATABASE [LogFileRead_Test]
MODIFY FILE
(
    NAME = LogFileRead_Test_log,
    SIZE = 128MB
);

-- Hekaton-yeah, I want memory optimized data
GO
ALTER DATABASE [LogFileRead_Test]
ADD FILEGROUP [LatencyTestInMemoryFileGroup] CONTAINS MEMORY_OPTIMIZED_DATA;

GO
ALTER DATABASE [LogFileRead_Test]
ADD FILE 
(
    NAME = [LatencyTestInMemoryFile], 
    FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL13.SQL2016\MSSQL\DATA\LogFileRead_Test_SessionStateInMemoryFile'
) TO FILEGROUP [LatencyTestInMemoryFileGroup];

GO
USE [LogFileRead_Test]

GO
CREATE TYPE [dbo].[InMemoryIdTable] AS TABLE (
    [InMemoryId] NVARCHAR (88) COLLATE Latin1_General_100_BIN2 NOT NULL,
    PRIMARY KEY NONCLUSTERED HASH ([InMemoryId]) WITH (BUCKET_COUNT = 240))
    WITH (MEMORY_OPTIMIZED = ON);

GO
CREATE TABLE [dbo].[InMemoryStuff] (
    [InMemoryId]   NVARCHAR (88)    COLLATE Latin1_General_100_BIN2 NOT NULL,
    [Created]     DATETIME2 (7)    NOT NULL,
    CONSTRAINT [PK_InMemoryStuff_InMemoryId] PRIMARY KEY NONCLUSTERED HASH ([InMemoryId]) WITH (BUCKET_COUNT = 240)
)
WITH (MEMORY_OPTIMIZED = ON);

GO
-- RBAR is the new black (we need some logs to read)
declare @j int = 0;
while @j < 100000
begin
    INSERT INTO [dbo].[InMemoryStuff](InMemoryId, Created) VALUES ('Description' + CAST(@j as varchar), GETDATE());
    set @j = @j + 1;
end

-- grab a baseline of virtual file stats to be diff'd later
select f.num_of_reads, f.num_of_bytes_read
into #dm_io_virtual_file_stats
from sys.dm_io_virtual_file_stats(default, default) f
where database_id = db_id('LogFileRead_Test') and file_id = FILE_IDEX('LogFileRead_Test_log');

-- hands off my log file, CHECKDB!
GO
DBCC CHECKDB ([LogFileRead_Test]) WITH NO_INFOMSGS, ALL_ERRORMSGS, DATA_PURITY;

-- grab the latest virtual file stats, and compare with the previous capture
GO
select f.num_of_reads, f.num_of_bytes_read
into #checkdb_stats
from sys.dm_io_virtual_file_stats(default, default) f
where database_id = db_id('LogFileRead_Test') and file_id = FILE_IDEX('LogFileRead_Test_log');

select 
        collection_time = GETDATE() 
        , num_of_reads = - f.num_of_reads + t.num_of_reads
        , num_of_bytes_read = - f.num_of_bytes_read + t.num_of_bytes_read
into #dm_io_virtual_file_stats_diff
from #dm_io_virtual_file_stats f, #checkdb_stats t;

drop table #checkdb_stats;
drop table #dm_io_virtual_file_stats;

-- CHECKDB ignored my comment
select collection_time, num_of_reads, num_of_bytes_read
from #dm_io_virtual_file_stats_diff d
order by d.collection_time;

drop table #dm_io_virtual_file_stats_diff;

-- I was *not* raised in a barn
USE [master];

ALTER DATABASE [LogFileRead_Test]
SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE [LogFileRead_Test];

Ponieważ to repozytorium generuje po prostu 1 lub 106 odczytów pliku dziennika, pomyślałem, że zagłębię się w 1 z sesją zdarzeń rozszerzonych file_read i file_read_completed.

name                timestamp                   mode        offset  database_id file_id size    duration
file_read           2018-04-06 10:51:11.1098141 Contiguous  72704   9           2       0       NULL    
file_read_completed 2018-04-06 10:51:11.1113345 Contiguous  72704   9           2       491520  1       

A oto szczegóły VLF ( DBCC LOGINFO()) dla kontekstu tych przesunięć i takie:

RecoveryUnitId  FileId  FileSize    StartOffset FSeqNo  Status  Parity  CreateLSN
0               2       2031616     8192        34      2       64      0
0               2       2031616     2039808     35      2       64      0
0               2       2031616     4071424     36      2       64      0
0               2       2285568     6103040     37      2       64      0
0               2       15728640    8388608     38      2       64      34000000005200001
0               2       15728640    24117248    39      2       64      34000000005200001
0               2       15728640    39845888    40      2       64      34000000005200001
0               2       15728640    55574528    0       0       0       34000000005200001
0               2       15728640    71303168    0       0       0       34000000005200001
0               2       15728640    87031808    0       0       0       34000000005200001
0               2       15728640    102760448   0       0       0       34000000005200001
0               2       15728640    118489088   0       0       0       34000000005200001

Tak więc operacja CHECKDB:

  • zaczął czytać 63 KB (64 512 bajtów) do pierwszego VLF,
  • odczytać 480 KB (491 520 bajtów) i
  • czy nie czytać ostatnią 1441 KB (1,475,584 bajtów) VLF

Przechwyciłem także callstacki, na wypadek, gdyby były pomocne.

file_read callstack:

(00007ffd`999a0860)   sqlmin!XeSqlPkg::file_read::Publish+0x1dc   |  (00007ffd`999a0b40)   sqlmin!XeSqlPkg::file_read_enqueued::Publish
(00007ffd`9a825e30)   sqlmin!FireReadEvent+0x118   |  (00007ffd`9a825f60)   sqlmin!FireReadEnqueuedEvent
(00007ffd`9980b500)   sqlmin!FCB::AsyncRead+0x74d   |  (00007ffd`9980b800)   sqlmin!FCB::AsyncReadInternal
(00007ffd`9970e9d0)   sqlmin!SQLServerLogMgr::LogBlockReadAheadAsync+0x6a6   |  (00007ffd`9970ec00)   sqlmin!LBH::Destuff
(00007ffd`9970a6d0)   sqlmin!LogConsumer::GetNextLogBlock+0x1591   |  (00007ffd`9970ab70)   sqlmin!LogPoolPrivateCacheBufferMgr::Lookup
(00007ffd`9a9fcbd0)   sqlmin!SQLServerLogIterForward::GetNext+0x258   |  (00007ffd`9a9fd2d0)   sqlmin!SQLServerLogIterForward::GetNextBlock
(00007ffd`9aa417f0)   sqlmin!SQLServerCOWLogIterForward::GetNext+0x2b   |  (00007ffd`9aa418c0)   sqlmin!SQLServerCOWLogIterForward::StartScan
(00007ffd`9aa64210)   sqlmin!RecoveryMgr::AnalysisPass+0x83b   |  (00007ffd`9aa65100)   sqlmin!RecoveryMgr::AnalyzeLogRecord
(00007ffd`9aa5ed50)   sqlmin!RecoveryMgr::PhysicalRedo+0x233   |  (00007ffd`9aa5f790)   sqlmin!RecoveryMgr::PhysicalCompletion
(00007ffd`9aa7fd90)   sqlmin!RecoveryUnit::PhysicalRecovery+0x358   |  (00007ffd`9aa802c0)   sqlmin!RecoveryUnit::CompletePhysical
(00007ffd`9a538b90)   sqlmin!StartupCoordinator::NotifyPhaseStart+0x3a   |  (00007ffd`9a538bf0)   sqlmin!StartupCoordinator::NotifyPhaseEnd
(00007ffd`9a80c430)   sqlmin!DBTABLE::ReplicaCreateStartup+0x2f4   |  (00007ffd`9a80c820)   sqlmin!DBTABLE::RefreshPostRecovery
(00007ffd`9a7ed0b0)   sqlmin!DBMgr::SyncAndLinkReplicaRecoveryPhase+0x890   |  (00007ffd`9a7edff0)   sqlmin!DBMgr::DetachDB
(00007ffd`9a7f2cd0)   sqlmin!DBMgr::CreatePhasedTransientReplica+0x869   |  (00007ffd`9a7f3630)   sqlmin!DBMgr::StrandTransientReplica
(00007ffd`9a7f2ae0)   sqlmin!DBMgr::CreateTransientReplica+0x118   |  (00007ffd`9a7f2cd0)   sqlmin!DBMgr::CreatePhasedTransientReplica
(00007ffd`99ec6d30)   sqlmin!DBDDLAgent::CreateReplica+0x1b5   |  (00007ffd`99ec6f90)   sqlmin!FSystemDatabase
(00007ffd`9abaaeb0)   sqlmin!UtilDbccCreateReplica+0x82   |  (00007ffd`9abab000)   sqlmin!UtilDbccDestroyReplica
(00007ffd`9ab0d7e0)   sqlmin!UtilDbccCheckDatabase+0x994   |  (00007ffd`9ab0ffd0)   sqlmin!UtilDbccRetainReplica
(00007ffd`9ab0cfc0)   sqlmin!DbccCheckDB+0x22d   |  (00007ffd`9ab0d380)   sqlmin!DbccCheckFilegroup
(00007ffd`777379c0)   sqllang!DbccCommand::Execute+0x193   |  (00007ffd`77737d70)   sqllang!DbccHelp
(00007ffd`777e58d0)   sqllang!CStmtDbcc::XretExecute+0x889   |  (00007ffd`777e6250)   sqllang!UtilDbccSetPermissionFailure
(00007ffd`76b02eb0)   sqllang!CMsqlExecContext::ExecuteStmts<1,1>+0x40d   |  (00007ffd`76b03410)   sqllang!CSQLSource::CleanupCompileXactState
(00007ffd`76b03a60)   sqllang!CMsqlExecContext::FExecute+0xa9e   |  (00007ffd`76b043d0)   sqllang!CCacheObject::Release
(00007ffd`76b03430)   sqllang!CSQLSource::Execute+0x981   |  (00007ffd`76b039b0)   sqllang!CSQLLock::Cleanup

file_read_completed callstack:

(00007ffd`99995cc0)   sqlmin!XeSqlPkg::file_read_completed::Publish+0x1fc   |  (00007ffd`99995fe0)   sqlmin!XeSqlPkg::file_write_completed::Publish
(00007ffd`9a826630)   sqlmin!FireIoCompletionEventLong+0x227   |  (00007ffd`9a8269c0)   sqlmin!IoRequestDispenser::Dump
(00007ffd`9969bee0)   sqlmin!FCB::IoCompletion+0x8e   |  (00007ffd`9969c180)   sqlmin!IoRequestDispenser::Put
(00007ffd`beaa11e0)   sqldk!IOQueue::CheckForIOCompletion+0x426   |  (00007ffd`beaa1240)   sqldk!SystemThread::GetCurrentId
(00007ffd`beaa15b0)   sqldk!SOS_Scheduler::SwitchContext+0x173   |  (00007ffd`beaa18a0)   sqldk!SOS_Scheduler::Switch
(00007ffd`beaa1d00)   sqldk!SOS_Scheduler::SuspendNonPreemptive+0xd3   |  (00007ffd`beaa1db0)   sqldk!SOS_Scheduler::ResumeNoCuzz
(00007ffd`99641720)   sqlmin!EventInternal<SuspendQueueSLock>::Wait+0x1e7   |  (00007ffd`99641ae0)   sqlmin!SOS_DispatcherPool<DispatcherWorkItem,DispatcherWorkItem,SOS_DispatcherQueue<DispatcherWorkItem,0,DispatcherWorkItem>,DispatcherPoolConfig,void * __ptr64>::GetDispatchers
(00007ffd`9aa437c0)   sqlmin!SQLServerLogMgr::CheckLogBlockReadComplete+0x1e6   |  (00007ffd`9aa44670)   sqlmin!SQLServerLogMgr::ValidateBlock
(00007ffd`9970a6d0)   sqlmin!LogConsumer::GetNextLogBlock+0x1b37   |  (00007ffd`9970ab70)   sqlmin!LogPoolPrivateCacheBufferMgr::Lookup
(00007ffd`9a9fcbd0)   sqlmin!SQLServerLogIterForward::GetNext+0x258   |  (00007ffd`9a9fd2d0)   sqlmin!SQLServerLogIterForward::GetNextBlock
(00007ffd`9aa417f0)   sqlmin!SQLServerCOWLogIterForward::GetNext+0x2b   |  (00007ffd`9aa418c0)   sqlmin!SQLServerCOWLogIterForward::StartScan
(00007ffd`9aa64210)   sqlmin!RecoveryMgr::AnalysisPass+0x83b   |  (00007ffd`9aa65100)   sqlmin!RecoveryMgr::AnalyzeLogRecord
(00007ffd`9aa5ed50)   sqlmin!RecoveryMgr::PhysicalRedo+0x233   |  (00007ffd`9aa5f790)   sqlmin!RecoveryMgr::PhysicalCompletion
(00007ffd`9aa7fd90)   sqlmin!RecoveryUnit::PhysicalRecovery+0x358   |  (00007ffd`9aa802c0)   sqlmin!RecoveryUnit::CompletePhysical
(00007ffd`9a538b90)   sqlmin!StartupCoordinator::NotifyPhaseStart+0x3a   |  (00007ffd`9a538bf0)   sqlmin!StartupCoordinator::NotifyPhaseEnd
(00007ffd`9a80c430)   sqlmin!DBTABLE::ReplicaCreateStartup+0x2f4   |  (00007ffd`9a80c820)   sqlmin!DBTABLE::RefreshPostRecovery
(00007ffd`9a7ed0b0)   sqlmin!DBMgr::SyncAndLinkReplicaRecoveryPhase+0x890   |  (00007ffd`9a7edff0)   sqlmin!DBMgr::DetachDB
(00007ffd`9a7f2cd0)   sqlmin!DBMgr::CreatePhasedTransientReplica+0x869   |  (00007ffd`9a7f3630)   sqlmin!DBMgr::StrandTransientReplica
(00007ffd`9a7f2ae0)   sqlmin!DBMgr::CreateTransientReplica+0x118   |  (00007ffd`9a7f2cd0)   sqlmin!DBMgr::CreatePhasedTransientReplica
(00007ffd`99ec6d30)   sqlmin!DBDDLAgent::CreateReplica+0x1b5   |  (00007ffd`99ec6f90)   sqlmin!FSystemDatabase
(00007ffd`9abaaeb0)   sqlmin!UtilDbccCreateReplica+0x82   |  (00007ffd`9abab000)   sqlmin!UtilDbccDestroyReplica
(00007ffd`9ab0d7e0)   sqlmin!UtilDbccCheckDatabase+0x994   |  (00007ffd`9ab0ffd0)   sqlmin!UtilDbccRetainReplica
(00007ffd`9ab0cfc0)   sqlmin!DbccCheckDB+0x22d   |  (00007ffd`9ab0d380)   sqlmin!DbccCheckFilegroup
(00007ffd`777379c0)   sqllang!DbccCommand::Execute+0x193   |  (00007ffd`77737d70)   sqllang!DbccHelp

Te ślady stosu korelują z odpowiedzią Maxa wskazującą, że CHECKDB używa wewnętrznej migawki pomimo obecności tabel Hekaton.

Przeczytałem, że migawki wykonują odzyskiwanie w celu cofnięcia niezatwierdzonych transakcji :

Niezatwierdzone transakcje są wycofywane w nowo utworzonej migawce bazy danych, ponieważ aparat bazy danych uruchamia odzyskiwanie po utworzeniu migawki (nie dotyczy to transakcji w bazie danych).

Ale to wciąż nie wyjaśnia, dlaczego duża część pliku dziennika jest często odczytywana w moim scenariuszu produkcyjnym (i czasami w dostarczonych tutaj repozytoriach). Nie sądzę, żebym miał tyle transakcji w locie w danej chwili w mojej aplikacji, a na pewno nie ma tu żadnych repro.

Josh Darnell
źródło

Odpowiedzi:

10

Chociaż dokumentacja programu SQL Server stwierdza, że ​​bazy danych z tabelami „In-Memory” nie obsługują migawek, wymagane migawki „wewnętrzne” DBCC CHECKDBmogą być nadal tworzone, ponieważ operacja checkdb nie dotyka tabel w pamięci, a migawka przechwytuje tylko zmiany do tabel na dysku.

Przypuszczalnie Microsoft postanowił zapobiegać tworzeniu migawek tworzonych przez użytkowników w bazach danych z tabelami w pamięci, ponieważ musiałyby duplikować struktury w pamięci, aby migawka była naprawdę kompletną migawką z normalnego, skoncentrowanego na użytkowniku sensu. Duplikowanie tabel w pamięci dla migawki może łatwo spowodować, że na serwerze zabraknie pamięci, co nie jest dobrą rzeczą ™

Możesz sam udowodnić, że tworzona jest wewnętrzna migawka DBCC, obserwując folder danych, w którym znajduje się główny plik danych bazy danych podczas działania DBCC CHECKDB. Jeśli tworzona jest wewnętrzna migawka, zobaczysz plik o nazwie LogFileRead_Test.mdf_MSSQL_DBCC7( 7może być inny - reprezentuje identyfikator bazy danych dla Twojej bazy danych).

Po utworzeniu pliku migawki SQL Server musi uruchomić odzyskiwanie w bazie danych, aby doprowadzić go do spójnego stanu wymaganego do działania DBCC CHECKDB. Każde działanie polegające na czytaniu dziennika może być wynikiem tego procesu odzyskiwania. Zbudowałem szybki zestaw do sprawdzania wyników wielu DBCC CHECKDBdziałań, co dowodzi, że jeśli nie ma transakcji między checkdbs, nie ma odczytów pliku dziennika.

USE master;
SET IMPLICIT_TRANSACTIONS OFF;
USE [master];
IF (DB_ID(N'LogFileRead_Test') IS NOT NULL) 
BEGIN
    ALTER DATABASE [LogFileRead_Test]
    SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
    DROP DATABASE [LogFileRead_Test];
END

CREATE DATABASE [LogFileRead_Test]
ALTER DATABASE [LogFileRead_Test]
MODIFY FILE
(
    NAME = LogFileRead_Test_log,
    SIZE = 128MB
);

ALTER DATABASE [LogFileRead_Test]
ADD FILEGROUP [LatencyTestInMemoryFileGroup] CONTAINS MEMORY_OPTIMIZED_DATA;
ALTER DATABASE [LogFileRead_Test]
ADD FILE 
(
    NAME = [LatencyTestInMemoryFile], 
    FILENAME = 'C:\temp\LogFileRead_Test_SessionStateInMemoryFile'
) TO FILEGROUP [LatencyTestInMemoryFileGroup];
GO

USE LogFileRead_Test;

CREATE TABLE [dbo].[InMemoryStuff] (
    [InMemoryId]   NVARCHAR (88)    COLLATE Latin1_General_100_BIN2 NOT NULL,
    [Created]     DATETIME2 (7)    NOT NULL,
    CONSTRAINT [PK_InMemoryStuff_InMemoryId] 
    PRIMARY KEY NONCLUSTERED 
    HASH ([InMemoryId]) WITH (BUCKET_COUNT = 240)
)
WITH (MEMORY_OPTIMIZED = ON);

;WITH src AS (
    SELECT n.Num
    FROM (VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9))n(Num)
)
INSERT INTO [dbo].[InMemoryStuff] (InMemoryId, Created) 
SELECT 'Description' + CONVERT(varchar(30)
        , ((s1.Num * 10000) 
         + (s2.Num * 1000) 
         + (s3.Num * 100) 
         + (s4.Num * 10) 
         + (s5.Num)))
    , GETDATE()
FROM src s1
    CROSS JOIN src s2
    CROSS JOIN src s3
    CROSS JOIN src s4
    CROSS JOIN src s5;
USE master;

DECLARE @cmd nvarchar(max);
DECLARE @msg nvarchar(1000);
DECLARE @l int;
DECLARE @m int;
SET @m = 10;
SET @l = 1;
IF OBJECT_ID(N'tempdb..#vfs', N'U') IS NOT NULL DROP TABLE #vfs;
CREATE TABLE #vfs (
    vfs_run int NOT NULL IDENTITY(1,1) PRIMARY KEY CLUSTERED
    , collection_time datetime2(7)
    , num_of_reads bigint
    , num_of_bytes_read bigint
);

WHILE @l <= @m 
BEGIN
SET @msg = N'loop ' + CONVERT(nvarchar(10), @l);
RAISERROR (@msg, 0, 1) WITH NOWAIT;

SET @cmd = 'USE [LogFileRead_Test];
-- grab a baseline of virtual file stats to be diff''d later
select f.num_of_reads, f.num_of_bytes_read
into #dm_io_virtual_file_stats
from sys.dm_io_virtual_file_stats(default, default) f
where database_id = db_id(''LogFileRead_Test'') and file_id = FILE_IDEX(''LogFileRead_Test_log'');

DBCC CHECKDB ([LogFileRead_Test]) WITH NO_INFOMSGS, ALL_ERRORMSGS, DATA_PURITY;

-- grab the latest virtual file stats, and compare with the previous capture
select f.num_of_reads, f.num_of_bytes_read
into #checkdb_stats
from sys.dm_io_virtual_file_stats(default, default) f
where database_id = db_id(''LogFileRead_Test'') and file_id = FILE_IDEX(''LogFileRead_Test_log'');

select 
        collection_time = GETDATE() 
        , num_of_reads = - f.num_of_reads + t.num_of_reads
        , num_of_bytes_read = - f.num_of_bytes_read + t.num_of_bytes_read
into #dm_io_virtual_file_stats_diff
from #dm_io_virtual_file_stats f, #checkdb_stats t;

--drop table #checkdb_stats;
--drop table #dm_io_virtual_file_stats;

-- CHECKDB ignored my comment
select collection_time, num_of_reads, num_of_bytes_read
from #dm_io_virtual_file_stats_diff d
order by d.collection_time;

--drop table #dm_io_virtual_file_stats_diff;
';
INSERT INTO #vfs (collection_time, num_of_reads, num_of_bytes_read)
EXEC sys.sp_executesql @cmd;

SET @l += 1;
END

USE master;
SET @cmd = 'USE [master];
ALTER DATABASE [LogFileRead_Test]
SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE [LogFileRead_Test];
';
EXEC sys.sp_executesql @cmd;

SELECT *
FROM #vfs
ORDER BY vfs_run;

Wyniki:

╔═════════╦═════════════════════════════╦═════════ ═════╦═══════════════════╗
║ vfs_run ║ czas_kolekcji ║ liczba_przeczytanych ║ liczba_poczytanych_przeczytania ║
╠═════════╬═════════════════════════════╬═════════ ═════╬═══════════════════╣
║ 1 ║ 2018-04-06 15: 53: 37.6566667 ║ 1 ║ 491520 ║
║ 2 ║ 2018-04-06 15: 53: 37.8300000 ║ 0 ║ 0 ║
║ 3 ║ 2018-04-06 15: 53: 38.0166667 ║ 0 ║ 0 ║
║ 4 ║ 2018-04-06 15: 53: 38.1866667 ║ 0 ║ 0 ║
║ 5 ║ 2018-04-06 15: 53: 38.3766667 ║ 0 ║ 0 ║
║ 6 ║ 2018-04-06 15: 53: 38.5633333 ║ 0 ║ 0 ║
║ 7 ║ 2018-04-06 15: 53: 38.7333333 ║ 0 ║ 0 ║
║ 8 ║ 2018-04-06 15: 53: 38.9066667 ║ 0 ║ 0 ║
║ 9 ║ 2018-04-06 15: 53: 39.0933333 ║ 0 ║ 0 ║
║ 10 ║ 2018-04-06 15: 53: 39,2800000 ║ 0 ║ 0 ║
╚═════════╩═════════════════════════════╩═════════ ═════╩═══════════════════╝

Ponadto zamiast używać metody RBAR do wstawiania danych do tabeli testowej, możesz zastosować proste podejście oparte na zestawie, takie jak to poniżej:

;WITH src AS (
    SELECT n.Num
    FROM (VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9))n(Num)
)
INSERT INTO [dbo].[InMemoryStuff] (InMemoryId, Created) 
SELECT 'Description' + CONVERT(varchar(30)
     , ((s1.Num * 10000) 
      + (s2.Num * 1000) 
      + (s3.Num * 100) 
      + (s4.Num * 10) 
      + (s5.Num)))
    , GETDATE()
FROM src s1
    CROSS JOIN src s2
    CROSS JOIN src s3
    CROSS JOIN src s4
    CROSS JOIN src s5;

W moim teście wypełnia on tabelę w niecałe 3 sekundy, podczas gdy podejście RBAR zajmuje dużo czasu . Również miłe komentarze w twoim kodzie sprawiły, że lol.

Max Vernon
źródło