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

Я хочу создать таблицу в MS SQL Server 2005 для записи сведений о некоторых системных операциях. Как видно из приведенной ниже схемы таблицы, каждый столбец, кроме 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,
[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) помогает ли это сохранить размер таблицы меньшим? Или я просто пытаюсь использовать базу данных и ничего не достичь?

Ответы [ 6 ]

27 голосов
/ 09 ноября 2009

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

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

альтернативный текст 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 теперь являются текстовыми указателями в блоке выделения больших объектов:

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)                    

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

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

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

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 дает тот же результат при бесплатной стоимости разработки / тестирования.

10 голосов
/ 09 ноября 2009

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

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

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

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

2 голосов
/ 09 ноября 2009

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

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

0 голосов
/ 10 ноября 2009

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

0 голосов
/ 09 ноября 2009

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

0 голосов
/ 09 ноября 2009

Держите это встроенным.Весь смысл varchar в том, что он занимает 0 байт, если он пуст, 4 байта для «Hello» и т. Д.

...