Должен ли я использовать встроенный столбец varchar (max) или хранить его в отдельной таблице?

Я хочу создать таблицу в MS SQL Server 2005 для записи сведений о некоторых системных операциях. Как вы можете видеть из нижеприведенного дизайна таблицы, каждый столбец, кроме Details, не имеет значения NULL.

CREATE TABLE [Log]
(
[LogID] [int] IDENTITY(1,1) NOT NULL,
[ActionID] [int] NOT NULL,
[SystemID] [int] NOT NULL,
[UserID] [int] NOT NULL,
[LoggedOn] [datetime] NOT NULL,
[Details] [varchar](max) NULL
)

Поскольку столбец Details не всегда будет иметь в нем данные. Является ли более эффективным сохранение этого столбца в отдельной таблице и предоставление ссылки на него вместо этого?

CREATE TABLE [Log]
(
[LogID] [int] IDENTITY(1,1) NOT NULL,
[ActionID] [int] NOT NULL,
[SystemID] [int] NOT NULL,
[UserID] [int] NOT NULL,
[LoggedOn] [datetime] NOT NULL,
[DetailID] [int] NULL
)       

CREATE TABLE [Detail]
(
[DetailID] [int] IDENTITY(1,1) NOT NULL,
[Details] [varchar](max) NOT NULL
)

Для меньшего типа данных я бы не стал его рассматривать, но для varchar(max) делает ли это помощь, чтобы уменьшить размер таблицы? Или я просто пытаюсь извлечь умную базу данных и ничего не добиваться?

Ответы

Ответ 1

Храните его в строгом соответствии. Покрытия SQL Server уже хранит столбцы MAX в отдельной "единице распределения" с SQL 2005. См. Таблица и организация индексов. Фактически это точно так же, как хранение столбца MAX в собственной таблице, но без какого-либо недостатка явно.

Наличие явной таблицы на самом деле будет медленнее (из-за ограничения внешнего ключа) и потребляет больше места (из-за дублирования DetaiID). Не говоря уже о том, что для этого требуется больше кода, а ошибки вводятся... написанием кода.

alt text http://i.msdn.microsoft.com/ms189051.3be61595-d405-4b30-9794-755842d7db7e(en-us,SQL.100).gif

Обновление

Чтобы проверить фактическое местоположение данных, простой тест может показать это:

use tempdb;
go

create table a (
  id int identity(1,1) not null primary key,
  v_a varchar(8000),
  nv_a nvarchar(4000),
  m_a varchar(max),
  nm_a nvarchar(max),
  t text,
  nt ntext);
go

insert into a (v_a, nv_a, m_a, nm_a, t, nt)
values ('v_a', N'nv_a', 'm_a', N'nm_a', 't', N'nt');
go

select %%physloc%%,* from a
go

Псевдо-столбец %%physloc%% покажет фактическое физическое местоположение строки, в моем случае это была страница 200:

dbcc traceon(3604)
dbcc page(2,1, 200, 3)

Slot 0 Column 2 Offset 0x19 Length 3 Length (physical) 3
v_a = v_a                            
Slot 0 Column 3 Offset 0x1c Length 8 Length (physical) 8
nv_a = nv_a                          
m_a = [BLOB Inline Data] Slot 0 Column 4 Offset 0x24 Length 3 Length (physical) 3
m_a = 0x6d5f61                       
nm_a = [BLOB Inline Data] Slot 0 Column 5 Offset 0x27 Length 8 Length (physical) 8
nm_a = 0x6e006d005f006100            
t = [Textpointer] Slot 0 Column 6 Offset 0x2f Length 16 Length (physical) 16
TextTimeStamp = 131137536            RowId = (1:182:0)                    
nt = [Textpointer] Slot 0 Column 7 Offset 0x3f Length 16 Length (physical) 16
TextTimeStamp = 131203072            RowId = (1:182:1)   

Все значения столбцов, но TEXT и NTEXT, были сохранены в строке, включая типы MAX.
После изменения параметров таблицы и вставки новой строки (sp_tableoption не влияет на существующие строки), типы MAX были выселены в их собственное хранилище:

sp_tableoption 'a' , 'large value types out of row', '1';
insert into a (v_a, nv_a, m_a, nm_a, t, nt)
values ('2v_a', N'2nv_a', '2m_a', N'2nm_a', '2t', N'2nt');    
dbcc page(2,1, 200, 3);

Обратите внимание, что столбцы m_a и nm_a теперь являются Textpointer в блоке выделения LOB:

Slot 1 Column 2 Offset 0x19 Length 4 Length (physical) 4
v_a = 2v_a                           
Slot 1 Column 3 Offset 0x1d Length 10 Length (physical) 10
nv_a = 2nv_a                         
m_a = [Textpointer] Slot 1 Column 4 Offset 0x27 Length 16 Length (physical) 16
TextTimeStamp = 131268608            RowId = (1:182:2)                    
nm_a = [Textpointer] Slot 1 Column 5 Offset 0x37 Length 16 Length (physical) 16
TextTimeStamp = 131334144            RowId = (1:182:3)                    
t = [Textpointer] Slot 1 Column 6 Offset 0x47 Length 16 Length (physical) 16
TextTimeStamp = 131399680            RowId = (1:182:4)                    
nt = [Textpointer] Slot 1 Column 7 Offset 0x57 Length 16 Length (physical) 16
TextTimeStamp = 131465216            RowId = (1:182:5)                    

Для незавершенности завершения мы также можем принудительно вывести одно из немакс-полей из строки:

update a set v_a = replicate('X', 8000);
dbcc page(2,1, 200, 3);

Обратите внимание, как столбец v_a хранится в хранилище Row-Overflow:

Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4
v_a = [BLOB Inline Root] Slot 0 Column 2 Offset 0x19 Length 24 Length (physical) 24
Level = 0                            Unused = 99                          UpdateSeq = 1
TimeStamp = 1098383360               
Link 0
Size = 8000                          RowId = (1:176:0) 

Итак, как уже отмечали другие, типы MAX хранятся в строках по умолчанию, если они подходят. Для многих проектов DW это было бы неприемлемо, потому что типичные нагрузки DW должны сканировать или, по крайней мере, сканировать диапазон, поэтому следует использовать sp_tableoption ..., 'large value types out of row', '1'. Обратите внимание, что это не влияет на существующие строки, в моем тесте даже не на перестройку индекса, поэтому опция должна быть включена раньше.

Для большинства загрузок типа OLTP, хотя факт, что типы MAX хранятся в строке, если это возможно, на самом деле является преимуществом, поскольку шаблон доступа OLTP предназначен для поиска, а ширина строки мало влияет на него.

Тем не менее, в отношении исходного вопроса: отдельная таблица не нужна. Включение опции large value types out of row позволяет получить тот же результат при бесплатной стоимости разработки/теста.

Ответ 2

Как ни парадоксально, если ваши данные обычно составляют менее 8000 символов, я бы сохранил их в отдельной таблице, а если данные больше 8000 символов, я бы сохранил их в одной таблице.

Это происходит из-за того, что SQL Server хранит данные на странице, если разрешает строке сидеть на одной странице, но когда данные становятся больше, она перемещает ее точно так же, как и тип данных TEXT, и оставляет только указатель в строке. Таким образом, для группы из 3000 символов, вы устанавливаете меньше строк на страницу, что действительно неэффективно, но для группы из 12000 строк символов данные находятся вне строки, поэтому они фактически более эффективны.

Сказав это, обычно у вас есть широкий диапазон длин, и, таким образом, я переместил бы его в свою таблицу. Это дает вам гибкость для перемещения этой таблицы в другую группу файлов и т.д.

Обратите внимание, что вы также можете указать его для принудительного вывода данных из строки с помощью sp_tableoption. varchar (max) в основном похож на тип данных TEXT, при этом он по умолчанию выполняет данные в строке (для varchar (max)) вместо того, чтобы не выполнять данные из строки (для TEXT).

Ответ 3

Вы должны структурировать свои данные во все, что кажется наиболее логичной структурой, и позволить SQL Server выполнять свои оптимизации по физическому хранению данных.

Если через анализ производительности вы обнаружите, что ваша структура является проблемой производительности, рассмотрите возможность внесения изменений в структуру или настройки хранилища.

Ответ 4

Храните его в строгом соответствии. Вся точка varchar заключается в том, что она занимает 0 байт, если она пуста, 4 байта для "Hello" и т.д.

Ответ 5

Я бы нормализовал его, создав таблицу Detail. Я предполагаю, что некоторые записи в журнале будут иметь тот же деталь? Поэтому, если вы его нормализуете, вы будете хранить только код FK id INTEGER вместо текста для каждого случая, если вы сохранили текст в таблице Detail. Если у вас есть причины для де-нормализации, сделайте это, но из вашего вопроса я не вижу, что это так.

Ответ 6

Наличие столбца с нулевым значением стоит 2 байта для каждых 16 из них. Если это единственный (или 17-й или 33-й и т.д.) Столбцы с нулевым значением в таблице, это будет стоить вам 2 байта за строку, в противном случае ничего.