После того как я написал FILESTREAM Whitepaper для Microsoft, мне стали часто задавать вопросы о структуре контейнера данных FILESTREAM. Контейнер данных FILESTREAM — это технический термин для структуры каталогов NTFS, где хранятся все данные FILESTREAM.
Чтобы использовать данные FILESTREAM, сначала нужно добавить файловую группу (во время создания базы данных или после):
ALTER DATABASE [FileStreamTestDB] ADD FILEGROUP [FileStreamGroup1] CONTAINS FILESTREAM;
GO
Затем добавить «файл» в файловую группу:
ALTER DATABASE [FileStreamTestDB] ADD FILE (
NAME = [FSGroup1File], FILENAME = N'C:\Metro Labs\FileStreamTestDB\Documents')
TO FILEGROUP [FileStreamGroup1];
GO
На самом деле «файл» — это путь к тому, что станет корневым каталогом контейнера данных FILESTREAM. При первоначальном создании он будет содержать один файл filestream.hdr
и один каталог _$FSLOG_
. Файл filestream.hdr
— это метаданные, описывающие контейнер данных, а каталог _$FSLOG_
— FILESTREAM‑аналог журнала транзакций базы данных. Их можно считать концептуально эквивалентными, хотя у журнала FILESTREAM есть интересные особенности.
Чаще всего меня спрашивают: хранятся ли все файлы FILESTREAM для базы данных в одном огромном каталоге? Ответ: нет.
Корневой каталог контейнера данных содержит по одному подкаталогу на каждую таблицу (или на каждый раздел, если таблица секционирована). Каждый из этих каталогов содержит ещё один подкаталог для каждого FILESTREAM‑столбца, определённого в таблице. Ниже приведён пример; снимок экрана был сделан после выполнения следующего кода:
CREATE TABLE [FileStreamTest1] (
[DocId] UNIQUEIDENTIFIER ROWGUIDCOL NOT NULL UNIQUE,
[DocName] VARCHAR(25),
[Document] VARBINARY(MAX) FILESTREAM);
GO
CREATE TABLE [FileStreamTest2] (
[DocId] UNIQUEIDENTIFIER ROWGUIDCOL NOT NULL UNIQUE,
[DocName] VARCHAR(25),
[Document1] VARBINARY(MAX) FILESTREAM,
[Document2] VARBINARY(MAX) FILESTREAM);
GO
INSERT INTO [FileStreamTest1] VALUES (NEWID(), 'Paul Randal', CAST('SQLskills.com' AS VARBINARY(MAX)));
INSERT INTO [FileStreamTest1] VALUES (NEWID(), 'Kimberly Tripp', CAST('SQLskills.com' AS VARBINARY(MAX)));
GO
Это изображение показывает контейнер данных FILESTREAM для нашей базы данных, в которой две таблицы со столбцами FILESTREAM, каждая — с одним разделом. Первая таблица имеет два FILESTREAM‑столбца, а вторая — один. Имена всех этих каталогов — GUID’ы; как сопоставить каталоги таблицам и столбцам, я объясняю в отдельной статье.
В примере можно увидеть два FILESTREAM‑файла в каталоге уровня столбца. Имена файлов FILESTREAM — это, по сути, номер LSN из журнала транзакций базы данных на момент создания файлов. Это можно сопоставить, просмотрев данные с помощью DBCC PAGE; но сперва нужно найти выделенные страницы, используя sp_AllocationMetadata
(см. соответствующую запись в блоге):
EXEC sp_AllocationMetadata FileStreamTest1;
GO
Object Name Index ID Alloc Unit ID Alloc Unit Type First Page Root Page First IAM Page
--------------- -------- ----------------- --------------- ---------- --------- --------------
FileStreamTest1 0 72057594039697408 IN_ROW_DATA (1:169) (0:0) (1:170)
FileStreamTest1 0 72057594039762944 ROW_OVERFLOW_DATA (0:0) (0:0) (0:0)
FileStreamTest1 2 72057594039828480 IN_ROW_DATA (1:171) (1:171) (1:172)
Обратите внимание: помимо кучи есть и некластеризованный индекс — это индекс, обеспечивающий уникальность в столбце UNIQUEIDENTIFIER
. Теперь можно использовать DBCC PAGE, чтобы посмотреть первую страницу кучи, на которой находятся наши записи данных:
DBCC TRACEON (3604);
DBCC PAGE (FileStreamTestDB, 1, 169, 3);
GO
(snip)
Slot 0 Offset 0x60 Length 88
Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS
Record Size = 88
Memory Dump @0x514EC060
00000000: 30001400 140d5047 2ca9f24f 874d35ca †0.....PG,©òOM5Ê
00000010: e9e77649 03000002 00280058 80506175 †éçvI.....(.X.Pau
00000020: 6c205261 6e64616c 03000000 00000080 †l Randal........
00000030: 140d5047 2ca9f24f 874d35ca e9e77649 †..PG,©òOM5ÊéçvI
00000040: 01000000 68020000 00000000 17000000 †....h...........
00000050: 79000000 0c000000 †††††††††††††††††††y.......
Slot 0 Column 1 Offset 0x4 Length 16 Length (physical) 16
DocId = 47500d14-a92c-4ff2-874d-35cae9e77649
Slot 0 Column 2 Offset 0x1d Length 11 Length (physical) 11
DocName = Paul Randal
Document = [Filestream column] Slot 0 Column 3 Offset 0x28 Length 48
ColType = 3 FileId = -2147483648 UpdateSeq = 1
CreateLSN = 00000017:00000079:000c (23:121:12) TxFMiniVer = 0
XdesId = (0:616)
(snip)
Видно, что значение CreateLSN выше совпадает с именем первого FILESTREAM‑файла на иллюстрации к примеру.
Надеюсь, теперь понятно, как хранятся файлы FILESTREAM — подробнее об этом будет в следующей статье, где я объясню, как реализованы изменения и сборка мусора.
Комментариев нет:
Отправить комментарий