tl; dr : чому CHECKDB читає журнал транзакцій для бази даних користувачів з оптимізованими для пам'яті таблицями?
Здається, що CHECKDB читає файл журналу транзакцій бази даних користувачів, коли він перевіряє одну з моїх баз даних, зокрема, базу даних, яка використовує таблиці OLTP в пам'яті.
CHECKDB для цієї бази даних все ще закінчується за розумну кількість часу, тому я здебільшого просто цікавлюсь поведінкою; але, безумовно, найдовша тривалість для CHECKDB з усіх баз даних у цьому випадку.
Переглядаючи епос Пола Рандала " CHECKDB from Every Angle: Повний опис усіх етапів CHECKDB ", я бачу, що перед SQL 2005 CHECKDB використовував для читання журналу, щоб отримати послідовний вигляд бази даних. Але оскільки це 2016 рік, він використовує внутрішній знімок бази даних.
Однак однією з передумов для знімків є:
Джерельна база даних не повинна містити групи файлів MEMORY_OPTIMIZED_DATA
У моїй базі даних користувачів є одна з таких файлових груп, тому схоже, що знімки не входять у стіл.
Відповідно до документів CHECKDB :
Якщо знімок неможливо створити або вказано TABLOCK, DBCC CHECKDB отримує блокування для отримання необхідної консистенції. У цьому випадку для виконання перевірок розподілу потрібен ексклюзивний блокування бази даних, а для виконання перевірок таблиці потрібні блоки спільної таблиці.
Гаразд, тому ми робимо блокування бази даних та таблиць замість знімків. Але це все ще не пояснює, чому він повинен читати журнал транзакцій. То що дає?
Я подав сценарій нижче, щоб відтворити сценарій. Він використовується sys.dm_io_virtual_file_stats
для ідентифікації зчитування файлу журналу.
Зауважте, що більшість часу він читає невелику частину журналу (480 КБ), але час від часу читає набагато більше (48,2 МБ). У моєму виробничому сценарії він зчитує більшість файлів журналу (~ 1,3 ГБ файлу 2 ГБ) щовечора опівночі, коли ми запускаємо CHECKDB.
Ось приклад результатів, які я поки що отримав зі сценарієм:
collection_time num_of_reads num_of_bytes_read
2018-04-04 15:12:29.203 106 50545664
Або це:
collection_time num_of_reads num_of_bytes_read
2018-04-04 15:25:14.227 1 491520
Якщо я заміню об'єкти, оптимізовані для пам'яті, звичайними таблицями, результат виглядає так:
collection_time num_of_reads num_of_bytes_read
2018-04-04 15:21:03.207 0 0
Чому CHECKDB читає файл журналу? І особливо, чому він час від часу читає набагато більшу частину файлу журналу?
Ось власне сценарій:
-- 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];
Оскільки це запитання загалом просто генерує 1 або 106 файлів журналу, я подумав, що я перекопаюсь до 1 із сеансом file_read та 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
А ось деталі VLF ( DBCC LOGINFO()
) для контексту цих компенсацій і таких:
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
Отже, операція CHECKDB:
- почав читати 63 КБ (64,512 байт) у перший VLF,
- читати 480 Кб (491 520 байт) та
- нічого НЕ прочитали останні тисячу чотиреста сорок одна КБ (1,475,584 байт) в УНЧ
Я також захопив дзвінки, якщо вони будуть корисними.
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
Ці стеки співвідносяться з відповіддю Макса, що вказує на те, що CHECKDB використовує внутрішній знімок, незважаючи на наявність таблиць Гекатона.
Я читав, що знімки виконують відновлення, щоб скасувати незапущені транзакції :
Незапущені транзакції повертаються назад у новоствореному знімку бази даних, оскільки двигун бази даних запускає відновлення після створення знімка (транзакції в базі даних не впливають).
Але це все ще не пояснює, чому в моєму виробничому сценарії часто читається великий фрагмент файлу журналу (а іноді і в репортажі, наданому тут). Я не думаю, що в моєму додатку є багато трансакційних польотів за даний момент часу, і тут, звичайно, немає жодних докорів.