У меня сложилось впечатление, что если бы я суммировал DATALENGTH()
все поля для всех записей в таблице, я бы получил общий размер таблицы. Я ошибаюсь?
SELECT
SUM(DATALENGTH(Field1)) +
SUM(DATALENGTH(Field2)) +
SUM(DATALENGTH(Field3)) TotalSizeInBytes
FROM SomeTable
WHERE X, Y, and Z are true
Я использовал этот запрос ниже (который я получил из Интернета, чтобы получить размеры таблиц, только кластеризованные индексы, чтобы он не включал индексы NC), чтобы получить размер конкретной таблицы в моей базе данных. Для выставления счетов (мы взимаем с наших отделов количество места, которое они используют), мне нужно выяснить, сколько места каждый отдел использовал в этой таблице. У меня есть запрос, который определяет каждую группу в таблице. Мне просто нужно выяснить, сколько места занимает каждая группа.
Пространство на строку может VARCHAR(MAX)
сильно колебаться из-за полей в таблице, поэтому я не могу просто взять средний размер * соотношение строк для отдела. Когда я использую DATALENGTH()
описанный выше подход, я получаю только 85% от общего пространства, используемого в запросе ниже. Мысли?
SELECT
s.Name AS SchemaName,
t.NAME AS TableName,
p.rows AS RowCounts,
(SUM(a.total_pages) * 8)/1024 AS TotalSpaceMB,
(SUM(a.used_pages) * 8)/1024 AS UsedSpaceMB,
((SUM(a.total_pages) - SUM(a.used_pages)) * 8)/1024 AS UnusedSpaceMB
FROM
sys.tables t with (nolock)
INNER JOIN
sys.schemas s with (nolock) ON s.schema_id = t.schema_id
INNER JOIN
sys.indexes i with (nolock) ON t.OBJECT_ID = i.object_id
INNER JOIN
sys.partitions p with (nolock) ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id
INNER JOIN
sys.allocation_units a with (nolock) ON p.partition_id = a.container_id
WHERE
t.is_ms_shipped = 0
AND i.OBJECT_ID > 255
AND i.type_desc = 'Clustered'
GROUP BY
t.Name, s.Name, p.Rows
ORDER BY
TotalSpaceMB desc
Было предложено создать отфильтрованный индекс для каждого отдела или раздела таблицы, чтобы я мог напрямую запрашивать пространство, используемое для каждого индекса. Отфильтрованные индексы можно создавать программно (и снова отбрасывать во время окна обслуживания или когда мне нужно выполнять периодическое выставление счетов) вместо постоянного использования пространства (в этом отношении лучше использовать разделы).
Мне нравится это предложение, и обычно я это делаю. Но, если честно, я использую «каждый отдел» в качестве примера, чтобы объяснить, зачем мне это нужно, но, честно говоря, это не совсем так. Из-за соображений конфиденциальности я не могу объяснить точную причину, почему мне нужны эти данные, но они аналогичны различным отделам.
Относительно некластеризованных индексов в этой таблице: Если бы я мог получить размеры индексов NC, это было бы здорово. Тем не менее, индексы NC составляют <1% от размера кластеризованного индекса, поэтому мы в порядке, не включая эти. Однако как бы мы включили индексы NC в любом случае? Я даже не могу получить точный размер для кластерного индекса :)
Ответы:
Please note that the following info is not intended to be a comprehensive
description of how data pages are laid out, such that one can calculate
the number of bytes used per any set of rows, as that is very complicated.
Данные - это не единственное, что занимает место на странице данных 8k:
Есть зарезервированное пространство. Вам разрешено использовать только 8060 из 8192 байтов (это 132 байта, которые никогда не были вашими в первую очередь):
DBCC PAGE
, поэтому здесь оно хранится отдельно, а не включается в информацию о каждой строке ниже.NULL
. 1 байт на каждый набор из 8 столбцов. И для всех столбцов, даже дляNOT NULL
них. Следовательно, минимум 1 байт.ALLOW_SNAPSHOT_ISOLATION ON
илиREAD_COMMITTED_SNAPSHOT ON
).LOB-указатели для данных, которые не хранятся в строке. Так что это будет учитывать
DATALENGTH
+ pointer_size. Но они не стандартного размера. Пожалуйста, обратитесь к следующему сообщению в блоге для получения подробной информации по этой сложной теме: Каков размер указателя большого объекта для (MAX) типов, таких как Varchar, Varbinary, Etc? , Между этой ссылкой и некоторым дополнительным тестированием, которое я провел , правила (по умолчанию) должны быть следующими:TEXT
,NTEXT
иIMAGE
):text in row
параметра, то:VARCHAR(MAX)
,NVARCHAR(MAX)
иVARBINARY(MAX)
):large value types out of row
, то всегда используется 16-байтовый указатель на хранилище больших объектов.Страницы переполнения больших объектов: если значение равно 10 КБ, для этого потребуется 1 полная страница 8 КБ переполнения, а затем часть 2-й страницы. Если никакие другие данные не могут занять оставшееся пространство (или даже разрешено, я не уверен в этом правиле), то у вас есть приблизительно 6 КБ «потерянного» пространства в этой 2-й странице данных переполнения большого объекта.
Неиспользуемое пространство: страница данных 8 КБ - это всего лишь 8192 байта. Он не отличается по размеру. Однако размещенные на нем данные и метаданные не всегда хорошо вписываются во все 8192 байта. И строки не могут быть разделены на несколько страниц данных. Таким образом, если у вас осталось 100 байт, но ни одна строка (или ни одна строка, которая бы не подходила в этом месте, в зависимости от нескольких факторов) не могла бы туда поместиться, страница данных по-прежнему занимает 8192 байта, а ваш второй запрос только подсчитывает количество страницы данных. Вы можете найти это значение в двух местах (просто имейте в виду, что некоторая часть этого значения составляет некоторое количество этого зарезервированного пространства):
DBCC PAGE( db_name, file_id, page_id ) WITH TABLERESULTS;
ИщитеParentObject
= "СТРАНИЦА ЗАГОЛОВОК:" иField
= "m_freeCnt".Value
Поле число неиспользованных байтов.SELECT buff.free_space_in_bytes FROM sys.dm_os_buffer_descriptors buff WHERE buff.[database_id] = DB_ID(N'db_name') AND buff.[page_id] = page_id;
Это то же значение, о котором сообщает "m_freeCnt". Это проще, чем DBCC, поскольку он может получить много страниц, но также требует, чтобы страницы были прочитаны в буферный пул в первую очередь.Пространство зарезервировано
FILLFACTOR
<100. Недавно созданные страницы не соответствуютFILLFACTOR
настройке, но выполнение REBUILD зарезервирует это место на каждой странице данных. Идея, стоящая за зарезервированным пространством, заключается в том, что оно будет использоваться непоследовательными вставками и / или обновлениями, которые уже увеличивают размер строк на странице из-за того, что столбцы переменной длины обновляются немного большим количеством данных (но этого недостаточно, чтобы вызвать страница-сплит). Но вы можете легко зарезервировать место на страницах данных, которые, естественно, никогда не получат новые строки и никогда не обновят существующие строки или, по крайней мере, не обновят таким образом, чтобы увеличить размер строки.Разделение страниц (фрагментация). Необходимость добавления строки в местоположение, в котором нет места для строки, приведет к разделению страницы. В этом случае около 50% существующих данных перемещается на новую страницу, а новая строка добавляется на одну из двух страниц. Но теперь у вас есть немного больше свободного места, которое не учитывается в
DATALENGTH
расчетах.Строки помечены для удаления. При удалении строк они не всегда сразу удаляются со страницы данных. Если они не могут быть удалены немедленно, они «помечены для смерти» (ссылка Стивена Сегала) и будут физически удалены позже процессом очистки призрака (я полагаю, что это имя). Однако они могут не относиться к данному конкретному Вопросу.
Призрачные страницы? Не уверен, что это правильный термин, но иногда страницы данных не удаляются, пока не будет выполнено REBUILD Кластерного индекса. Это также будет учитывать больше страниц, чем
DATALENGTH
в сумме. Обычно этого не должно происходить, но я столкнулся с этим один раз, несколько лет назад.SPARSE столбцы: разреженные столбцы экономят пространство (в основном для типов данных фиксированной длины) в таблицах, где большой процент строк предназначен
NULL
для одного или нескольких столбцов.SPARSE
Вариант делаетNULL
тип значения до 0 байт (вместо нормального количества фиксированной длиной, например , как 4 байта дляINT
), но , не нулевых значений каждого занимает еще 4 байта для типов фиксированной длины и количества переменного для типы переменной длины. Проблема здесь заключается в том, чтоDATALENGTH
в столбец SPARSE не входят дополнительные 4 байта для значений, отличных от NULL, поэтому эти 4 байта необходимо добавить обратно. Вы можете проверить, есть ли какие-либоSPARSE
столбцы с помощью:И затем для каждого
SPARSE
столбца обновите исходный запрос, чтобы использовать:Обратите внимание, что приведенный выше расчет для добавления в стандартные 4 байта немного упрощен, поскольку он работает только для типов фиксированной длины. И, есть дополнительные метаданные в строке (из того, что я могу пока сказать), которые уменьшают пространство, доступное для данных, просто имея хотя бы один столбец SPARSE. Дополнительные сведения см. На странице MSDN « Использование разреженных столбцов» .
Индексные и другие (например, IAM, PFS, GAM, SGAM и т. Д.) Страницы: это не страницы данных в терминах пользовательских данных. Это приведет к увеличению общего размера таблицы. Если вы используете SQL Server 2012 или новее, вы можете использовать
sys.dm_db_database_page_allocations
функцию динамического управления (DMF) для просмотра типов страниц (более ранние версии SQL Server могут использоватьDBCC IND(0, N'dbo.table_name', 0);
):Ни то,
DBCC IND
ни другоеsys.dm_db_database_page_allocations
(с этим условием WHERE) не будет сообщать о страницах индекса, и только о нихDBCC IND
будет сообщаться хотя бы одна страница IAM.DATA_COMPRESSION: если у вас включено
ROW
илиPAGE
включено сжатие в кластерном индексе или куче, то вы можете забыть о большинстве из того, что было упомянуто до сих пор. 96-байтовый заголовок страницы, массив байтов на 2 байта на строку и информация о версии 14 байтов на строку все еще там, но физическое представление данных становится очень сложным (намного больше, чем то, что уже упоминалось при сжатии). не используется). Например, при сжатии строк SQL Server пытается использовать наименьший возможный контейнер для размещения каждого столбца в каждой строке. Таким образом, если у вас естьBIGINT
столбец, который в противном случае (при условии, чтоSPARSE
он также не включен) всегда будет занимать 8 байтов, если значение находится в диапазоне от -128 до 127 (т. Е. 8-разрядное целое число со знаком), он будет использовать только 1 байт, и если значение может вписаться вSMALLINT
, это займет всего 2 байта. Типы Integer , которые являются либоNULL
или0
не занимают места и просто указаны какNULL
или «пустой» (то есть0
) в отображении массива из столбцов. И есть много, много других правил. Есть данные Unicode (NCHAR
,NVARCHAR(1 - 4000)
но неNVARCHAR(MAX)
, даже если хранятся в строке)? Сжатие Unicode было добавлено в SQL Server 2008 R2, но нет способа предсказать результат «сжатого» значения во всех ситуациях без фактического сжатия, учитывая сложность правил .Таким образом, ваш второй запрос, хотя и более точный с точки зрения общего физического пространства, занимаемого на диске, является действительно точным только после выполнения
REBUILD
кластерного индекса. И после этого вам все равно нужно учитывать любойFILLFACTOR
параметр ниже 100. И даже в этом случае всегда есть заголовки страниц, и часто достаточно некоторого количества «потраченного впустую» пространства, которое просто невозможно заполнить из-за того, что он слишком мал, чтобы поместиться в любой строке в этом таблица, или, по крайней мере, строка, которая логически должна идти в этом слоте.Что касается точности 2-го запроса при определении «использования данных», наиболее справедливо было бы отменить байты заголовка страницы, так как они не используют данные: это накладные расходы. Если на странице данных есть 1 строка, а эта строка - просто a
TINYINT
, то этот 1 байт все еще требует, чтобы страница данных существовала, и, следовательно, 96 байтов заголовка. Должен ли этот 1 отдел взимать плату за всю страницу данных? Если эта страница данных затем заполняется Департаментом № 2, будут ли они равномерно разделять эти «накладные» расходы или платить пропорционально? Кажется, проще всего просто отступить. В этом случае использование значения8
для умножения наnumber of pages
слишком велико. Как насчет:Следовательно, используйте что-то вроде:
для всех расчетов по столбцам "number_of_pages".
И , учитывая, что использование
DATALENGTH
для каждого поля не может возвращать мета-данные для каждой строки, которые должны быть добавлены к вашему запросу к таблице, где вы получаете дляDATALENGTH
каждого поля фильтрацию по каждому «отделу»:ALLOW_SNAPSHOT_ISOLATION
илиREAD_COMMITTED_SNAPSHOT
установлена наON
)NULL
, и если значение помещается в строку, то оно может быть намного меньше или намного больше, чем указатель, и если значение хранится вне строка, тогда размер указателя может зависеть от объема данных. Однако, поскольку мы просто хотим получить оценку (то есть «swag»), кажется, что 24 байта - это хорошее значение для использования (ну, как и любое другое ;-). Это для каждогоMAX
поля.Следовательно, используйте что-то вроде:
В общем (заголовок строки + количество столбцов + массив слотов + битовая карта NULL):
В общем (автоматическое определение, если присутствует «информация о версии»):
Если есть столбцы переменной длины, то добавьте:
Если есть какие-либо
MAX
столбцы / LOB, то добавьте:В общем:
Это не точно, и опять-таки не будет работать, если у вас включено сжатие строк или страниц в кучном или кластерном индексе, но вы должны определенно приблизиться.
ОБНОВЛЕНИЕ Относительно Тайны Разницы 15%
Мы (включая меня) были настолько сосредоточены на размышлениях о том, как устроены страницы данных и как
DATALENGTH
можно объяснить вещи, которые мы не тратили много времени на рассмотрение второго запроса. Я запустил этот запрос для одной таблицы, а затем сравнил эти значения с тем, о чем сообщалось,sys.dm_db_database_page_allocations
и они не были одинаковыми для количества страниц. По догадкам я удалил агрегатные функции иGROUP BY
и заменилSELECT
список наa.*, '---' AS [---], p.*
. И тогда стало ясно: люди должны быть осторожны, откуда в этих мутных сетях они получают информацию и сценарии ;-). Второй запрос, размещенный в Вопросе, не совсем корректен, особенно для этого конкретного Вопроса.Незначительная проблема: вне этого не имеет особого смысла
GROUP BY rows
(и не иметь этого столбца в статистической функции), соединениеsys.allocation_units
иsys.partitions
не является технически правильным. Существует 3 типа единиц распределения, и один из них должен присоединиться к другому полю. Довольно частоpartition_id
иhobt_id
тот же, так что не может быть проблемой, но иногда эти два поля имеют разные значения.Основная проблема: в запросе используется
used_pages
поле. Это поле охватывает все типы страниц: данные, индекс, IAM и т. Д. И т. Д. Существует еще один, более соответствующее поле для использования при касается только фактические данные:data_pages
.Я адаптировал 2-й запрос в Вопросе с учетом вышеупомянутых элементов и использовал размер страницы данных, который поддерживает заголовок страницы. Я также удалил два JOIN, которые были ненужными:
sys.schemas
(заменено на call toSCHEMA_NAME()
) иsys.indexes
(Clustered Index всегда есть,index_id = 1
и мыindex_id
в немsys.partitions
).источник
Может быть, это грандиозный ответ, но я бы так и сделал.
Таким образом, DATALENGTH составляют только 86% от общего числа. Это все еще очень представительный раскол. Накладные расходы в отличном ответе от srutzky должны иметь довольно равномерное разделение.
Я бы использовал ваш второй запрос (страниц) для общего количества. И используйте первое (длина данных) для выделения разделения. Многие затраты распределяются с использованием нормализации.
И вы должны учитывать, что более близкий ответ увеличит стоимость, так что даже отдел, который проиграл на сплите, все еще может заплатить больше.
источник