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 veri sayfasında yer alan tek şey veri değildir:
Ayrılmış alan var. Yalnızca 8192 baytın 8060'ını kullanmanıza izin verilir (bu, ilk etapta asla sizin olmayan 132 bayttır):
- Sayfa başlığı: Bu tam olarak 96 bayttır.
- Yuva dizisi: bu satır başına 2 bayttır ve her satırın sayfada başladığı uzaklığı gösterir. Bu dizinin boyutu kalan 36 baytla (132 - 96 = 36) sınırlı değildir, aksi takdirde bir veri sayfasına maksimum 18 satır koymakla etkin bir şekilde sınırlı kalırsınız. Bu, her satırın düşündüğünüzden 2 bayt daha büyük olduğu anlamına gelir. Bu değer, rapor edildiği gibi "kayıt boyutu" na dahil değildir
DBCC PAGE
, bu nedenle aşağıdaki satır başına bilgilere dahil olmak yerine burada ayrı tutulur.
- Satır Başına meta veriler (bunlarla sınırlı olmamak üzere):
- Boyut, tablo tanımına (yani sütun sayısı, değişken uzunluk veya sabit uzunluk vb.) Bağlı olarak değişir. Bu cevap ve testle ilgili tartışmada bulunan @ PaulWhite's ve @ Aaron'un yorumlarından alınan bilgiler .
- Satır başlığı: 2'si kayıt türünü gösteren 4 bayt ve diğer ikisi NULL Bitmap'e bir mahsup
- Sütun sayısı: 2 bayt
- NULL Bitmap: şu anda hangi sütunlar
NULL
. 8 sütunluk her set için 1 bayt. Ve tüm sütunlar, hatta sütunlar için NOT NULL
. Bu nedenle, en az 1 bayt.
- Değişken uzunlukta sütun ofseti dizisi: minimum 4 bayt. Değişken uzunluktaki sütunların sayısını tutmak için 2 bayt ve sonra da ofsetin başladığı yeri tutmak için her değişken uzunluktaki sütun başına 2 bayt.
- Sürüm Bilgisi: 14 bayt (eğer veritabanınız ya
ALLOW_SNAPSHOT_ISOLATION ON
da olarak ayarlanmışsa mevcut olacaktır READ_COMMITTED_SNAPSHOT ON
).
- Bununla ilgili daha fazla bilgi için lütfen aşağıdaki Soru ve Cevaplara bakın: Yuva Dizisi ve Toplam Sayfa Boyutu
- Lütfen veri sayfalarının nasıl düzenlendiğiyle ilgili birkaç ilginç ayrıntıya sahip olan Paul Randall'ın aşağıdaki blog yayınına bakın: DBCC PAGE ile uğraşmak (Bölüm 1?)
Satırda saklanmayan veriler için LOB işaretçileri. Böylece DATALENGTH
+ pointer_size açıklanır. Ancak bunlar standart büyüklükte değildir. Bu karmaşık konuyla ilgili ayrıntılar için lütfen aşağıdaki blog gönderisine bakın: Varchar, Varbinary, Etc gibi (MAX) Tipler için LOB İşaretçisinin Boyutu Nedir? . Bağlantılı yazı ile yaptığım bazı ek testler arasında , (varsayılan) kurallar aşağıdaki gibi olmalıdır:
- Eski / kimse (SQL Server 2005 itibarıyla artık kullanarak gerektiğini LOB türlerini kullanımdan kaldırıldı
TEXT
, NTEXT
ve IMAGE
):
- Varsayılan olarak, verilerini her zaman LOB sayfalarında saklayın ve LOB depolamaya her zaman 16 baytlık bir işaretçi kullanın.
- EĞER sp_tableoption ayarlamak için kullanıldı
text in row
, sonra seçeneği:
- sayfada değeri saklamak için alan varsa ve değer satır içi maksimum boyuttan büyük değilse (varsayılan 256 ile 7 - 7000 bayt arasında yapılandırılabilir aralık), satırda depolanır,
- aksi takdirde 16 baytlık bir işaretçi olacaktır.
- SQL Server 2005'te tanıtılan yeni LOB türleri için (
VARCHAR(MAX)
, NVARCHAR(MAX)
ve VARBINARY(MAX)
):
- Varsayılan olarak:
- Değeri büyüktür 8000 byte değil, varsa ve sayfadaki oda var, o zaman içinde satır saklanacaktır.
- Satır İçi Kök - 8001 ve 40.000 (gerçekten 42.000) bayt arasındaki veriler için, alan izin verirse, doğrudan LOB sayfalarına yönlendiren 1 ila 5 işaretçi (24 - 72 bayt) olacaktır. İlk 8k LOB sayfası için 24 bayt ve dört adede kadar 8k sayfa için her ek 8k sayfa başına 12 bayt.
- TEXT_TREE - 42.000 baytın üzerindeki veriler için veya 1 ila 5 işaretçi sıraya sığmazsa, LOB sayfalarına işaretçiler listesinin başlangıç sayfasında yalnızca 24 baytlık bir işaretçi olur (ör. "Text_tree" "sayfası).
- EĞER sp_tableoption ayarlamak için kullanılan
large value types out of row
seçenek, daha sonra her zaman LOB depolama için 16 baytlık işaretçisi kullanabilirsiniz.
- Ben yaptım "varsayılan" kurallar nedeniyle söz konusu değildir vb Veri Sıkıştırma, Sütun düzey Şifreleme, Şeffaf Veri Şifreleme, daima Şifreli gibi belirli özelliklerin etkisine karşı içinde satır değerleri test
LOB taşma sayfaları: Bir değer 10k ise, bu 1 tam 8k sayfa taşma ve daha sonra 2. sayfanın bir bölümünü gerektirir. Kalan alanı kaplayabilecek başka bir veri yoksa (veya bu kurala bile izin verilmiyorsa), bu 2. LOB taşma veri sayfasında yaklaşık 6 kb'lik "boşa" alanınız vardır.
Kullanılmayan alan: 8k veri sayfası şudur: 8192 bayt. Boyut olarak değişmez. Bununla birlikte, üzerine yerleştirilen veriler ve meta veriler her zaman 8192 baytın hepsine tam olarak uymaz. Ve satırlar birden çok veri sayfasına bölünemez. Dolayısıyla, 100 baytınız kaldı ancak satırınız (veya birkaç faktöre bağlı olarak o konuma sığacak hiçbir satır) oraya sığamazsa, veri sayfası hala 8192 bayt kaplıyor ve 2. sorgunuz yalnızca veri sayfaları. Bu değeri iki yerde bulabilirsiniz (bu değerin bir kısmının ayrılmış alanın bir kısmı olduğunu unutmayın):
DBCC PAGE( db_name, file_id, page_id ) WITH TABLERESULTS;
İçin bak ParentObject
= "Sayfa başlığı:" ve Field
= "m_freeCnt". Value
Alan kullanılmayan bayt sayısıdır.
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;
Bu, "m_freeCnt" tarafından bildirilen değerle aynıdır. Bu, birçok sayfa alabildiğinden DBCC'den daha kolaydır, ancak sayfaların ilk olarak arabellek havuzuna okunmasını gerektirir.
FILLFACTOR
<100 tarafından ayrılan alan. Yeni oluşturulan sayfalar FILLFACTOR
ayara uymaz, ancak REBUILD yapmak her veri sayfasında bu alanı ayıracaktır. Ayrılmış alanın arkasındaki fikir, değişken uzunluktaki sütunların biraz daha fazla veriyle güncellenmesi nedeniyle (ancak bir sayfa bölünmüş). Ancak, doğal olarak hiçbir zaman yeni satır almayan ve mevcut satırları hiçbir zaman güncellemeyen veya en azından satırın boyutunu artıracak şekilde güncellenmeyen veri sayfalarında yer ayırabilirsiniz.
Sayfa Bölmeleri (parçalanma): Satır için yer bulunmayan bir konuma satır eklemeniz gerektiğinde sayfa bölünmesine neden olur. Bu durumda, mevcut verilerin yaklaşık% 50'si yeni bir sayfaya taşınır ve yeni satır 2 sayfadan birine eklenir. Ama şimdi DATALENGTH
hesaplamalarla açıklanmayan biraz daha fazla boş alanınız var .
Silinmek üzere işaretlenmiş satırlar. Satırları sildiğinizde, satırlar her zaman veri sayfasından hemen kaldırılmaz. Hemen kaldırılamazlarsa, "ölüm için işaretlenir" (Steven Segal referansı) ve daha sonra hayalet temizleme işlemi ile fiziksel olarak kaldırılacaktır (bunun adı olduğuna inanıyorum). Ancak, bunlar bu Özel Soru ile ilgili olmayabilir.
Hayalet sayfalar? Bunun doğru terim olup olmadığından emin değilim, ancak bazen Veri Sayfaları Kümelenmiş Dizinin YENİDEN YAPILMASI yapılıncaya kadar kaldırılmaz. Bu, DATALENGTH
ekleyebileceğinden daha fazla sayfayı da hesaba katar. Bu genellikle olmamalı, ama birkaç yıl önce bir kez karşılaştım.
SPARSE sütunları: Seyrek sütunlar, satırların büyük bir yüzdesinin NULL
bir veya daha fazla sütun için olduğu tablolarda yerden tasarruf sağlar (çoğunlukla sabit uzunluktaki veri türleri için) . Bu SPARSE
seçenek, NULL
değer türünü 0 bayt yapar (an için 4 bayt gibi normal sabit uzunluk miktarı yerine INT
), ancak NULL olmayan değerlerin her biri sabit uzunluklu türler için ek 4 bayt ve değişken uzunluklu tipler. Buradaki sorun DATALENGTH
, bir SPARSE sütununda NULL olmayan değerler için fazladan 4 bayt içermemesidir, bu nedenle bu 4 baytın tekrar eklenmesi gerekir. SPARSE
Yoluyla herhangi bir sütun olup olmadığını kontrol edebilirsiniz :
SELECT OBJECT_SCHEMA_NAME(sc.[object_id]) AS [SchemaName],
OBJECT_NAME(sc.[object_id]) AS [TableName],
sc.name AS [ColumnName]
FROM sys.columns sc
WHERE sc.is_sparse = 1;
Ve sonra her SPARSE
sütun için kullanılacak orijinal sorguyu güncelleyin:
SUM(DATALENGTH(FieldN) + 4)
Standart 4 bayt eklemek için yukarıdaki hesaplamanın sadece basit uzunluklu türlerde çalıştığı için biraz basit olduğunu lütfen unutmayın. VE, satır başına ek veri (şimdiye kadar söyleyebileceğim kadarıyla) vardır, bu da sadece en az bir SPARSE sütununa sahip veriler için kullanılabilir alanı azaltır. Daha fazla ayrıntı için, lütfen Seyrek Sütunları Kullanma için MSDN sayfasına bakın .
Dizin ve diğer (örneğin IAM, PFS, GAM, SGAM vb.) Sayfalar: bunlar kullanıcı verileri açısından "veri" sayfaları değildir. Bunlar tablonun toplam boyutunu şişirir. SQL Server 2012 veya daha sys.dm_db_database_page_allocations
yenisini kullanıyorsanız , sayfa türlerini görmek için Dinamik Yönetim İşlevi'ni (DMF) kullanabilirsiniz (SQL Server'ın önceki sürümleri kullanabilir DBCC IND(0, N'dbo.table_name', 0);
):
SELECT *
FROM sys.dm_db_database_page_allocations(
DB_ID(),
OBJECT_ID(N'dbo.table_name'),
1,
NULL,
N'DETAILED'
)
WHERE page_type = 1; -- DATA_PAGE
Ne DBCC IND
de sys.dm_db_database_page_allocations
(bu WHERE yan tümcesiyle birlikte) herhangi bir Dizin sayfasını bildirmez ve yalnızca DBCC IND
en az bir IAM sayfası bildirir.
DATA_COMPRESSION: Kümelenmiş Dizin veya Öbek üzerinde Etkinleştirdiğiniz ROW
veya PAGE
Sıkıştırma özelliğini etkinleştirdiyseniz, şu ana kadar bahsedilenlerin çoğunu unutabilirsiniz. 96 bayt Sayfa Başlığı, satır başına 2 bayt Yuvası Dizisi ve satır başına 14 bayt Sürüm Bilgisi hala mevcuttur, ancak verilerin fiziksel gösterimi oldukça karmaşık hale gelir (Sıkıştırma sırasında daha önce bahsedilenlerden çok daha fazla) kullanılmıyor). Örneğin, Satır Sıkıştırma ile SQL Server, her satır başına, her sütuna sığdırmak için mümkün olan en küçük kapsayıcı kullanmaya çalışır. Bu nedenle BIGINT
, aksi halde ( SPARSE
etkin olmadığı varsayılırsa ) her zaman 8 bayt alırsa, değer -128 ile 127 arasındaysa (yani işaretli 8 bit tam sayı) varsa, yalnızca 1 bayt kullanır ve değer birSMALLINT
yalnızca 2 bayt alır. Boşluk alan NULL
veya 0
yer kaplamayan ve sütunları eşleyen bir dizide NULL
"boş" (yani 0
) olarak gösterilen tamsayı türleri . Ve daha birçok kural var. Var Unicode verileri ( NCHAR
, NVARCHAR(1 - 4000)
fakat değil NVARCHAR(MAX)
, içinde satır saklanan bile)? Unicode Sıkıştırma, SQL Server 2008 R2'ye eklendi, ancak kuralların karmaşıklığı göz önüne alındığında, gerçek sıkıştırmayı yapmadan tüm durumlarda "sıkıştırılmış" değerin sonucunu tahmin etmenin bir yolu yoktur .
Aslında, ikinci sorgunuz, diskte toplanan toplam fiziksel alan açısından daha doğru olsa REBUILD
da, Kümelenmiş Dizin'den biri yapıldığında gerçekten doğrudur . Ve bundan sonra, yine de FILLFACTOR
100'ün altındaki herhangi bir ayarı hesaba katmanız gerekir. Ve o zaman bile her zaman sayfa başlıkları vardır ve genellikle bu satırdaki herhangi bir satıra sığmayacak kadar küçük olduğu için doldurulamayan yeterli miktarda "boşa" alan vardır. tablo veya en azından mantıksal olarak o yuvaya girmesi gereken satır.
"Veri kullanımını" belirleme konusunda 2. sorgunun doğruluğuyla ilgili olarak, veri kullanımı olmadığı için Sayfa Üstbilgisi baytlarını geri almak en adil görünmektedir: bunlar işletme maliyeti yüküdür. Bir veri sayfasında 1 satır varsa ve bu satır yalnızca a ise TINYINT
, o 1 bayt hala veri sayfasının var olmasını ve dolayısıyla başlığın 96 baytını gerektirir. Bu 1 departman tüm veri sayfası için ücretlendirilmeli mi? Bu veri sayfası daha sonra Bölüm # 2 tarafından doldurulursa, bu "genel gider" maliyetini eşit olarak bölerler mi yoksa orantılı olarak öderler mi? Sadece geri almak en kolay görünüyor. Bu durumda, 8
çarpma değeri kullanmak number of pages
çok yüksektir. Nasıl olur:
-- 8192 byte data page - 96 byte header = 8096 (approx) usable bytes.
SELECT 8060.0 / 1024 -- 7.906250
Bu nedenle, şöyle bir şey kullanın:
(SUM(a.total_pages) * 7.91) / 1024 AS [TotalSpaceMB]
"number_of_pages" sütunlarına karşı tüm hesaplamalar için.
AND , DATALENGTH
her bir alan için kullanmanın satır başına meta verilerini döndüremeyeceğini düşünürsek, her bir alan için DATALENGTH
filtreleme yaparak her bir alan için filtreleme yaparak tablo başına sorgunuza eklenmelidir :
- Kayıt Türü ve ofset NULL Bitmap: 4 bayt
- Sütun Sayısı: 2 bayt
- Yuva Dizisi: 2 bayt ("kayıt boyutuna" dahil değildir, ancak yine de hesaba katılması gerekir)
- NULL Bitmap: 8 sütun başına 1 bayt ( tüm sütunlar için)
- Satır Sürüm Oluşturma: 14 bayt (veritabanından biri varsa
ALLOW_SNAPSHOT_ISOLATION
veya olarak READ_COMMITTED_SNAPSHOT
ayarlanırsa ON
)
- Değişken uzunlukta sütun Ofset Dizisi: Tüm sütunlar sabit uzunlukta ise 0 bayt. Herhangi bir sütun değişken uzunluktaysa, o zaman 2 bayt artı yalnızca değişken uzunluktaki sütunların her biri için 2 bayt.
- LOB işaretçileri: değer bir işaretçi olmayacağı için bu bölüm çok kesin değildir
NULL
ve değer satıra sığarsa, işaretçiden çok daha küçük veya çok daha büyük olabilir ve değer saklanırsa sonra, işaretçinin boyutu, ne kadar veri olduğuna bağlı olabilir. Ancak, sadece bir tahmin istediğimizden (yani "yağma"), 24 baytın kullanılması iyi bir değer gibi görünüyor (iyi, diğer herhangi bir şey gibi ;-). Bu, her MAX
alan için ayrıdır.
Bu nedenle, şöyle bir şey kullanın:
Genel olarak (satır başlığı + sütun sayısı + alan dizisi + NULL bitmap):
([RowCount] * (( 4 + 2 + 2 + (1 + (({NumColumns} - 1) / 8) ))
Genel olarak ("sürüm bilgisi" varsa otomatik algılama):
+ (SELECT CASE WHEN snapshot_isolation_state = 1 OR is_read_committed_snapshot_on = 1
THEN 14 ELSE 0 END FROM sys.databases WHERE [database_id] = DB_ID())
EĞER değişken uzunluklu sütunlar varsa, ekleyin:
+ 2 + (2 * {NumVariableLengthColumns})
Herhangi vardır EĞER MAX
/ LOB sütunları, daha sonra ekleyin:
+ (24 * {NumLobColumns})
Genel olarak:
)) AS [MetaDataBytes]
Bu kesin değildir ve Yığın veya Kümelenmiş Dizin'de Satır veya Sayfa Sıkıştırma'yı etkinleştirdiyseniz yine işe yaramaz, ancak kesinlikle sizi daha da yaklaştırmanız gerekir.
% 15 Fark Gizemine İlişkin GÜNCELLEME
Biz (kendim dahil) veri sayfalarının nasıl düzenlendiğini ve DATALENGTH
2. sorguyu gözden geçirmek için çok fazla zaman harcamadığımız şeyleri nasıl açıklayabileceğimizi düşünmeye odaklandık . Bu sorguyu tek bir tabloya karşı çalıştırdım ve daha sonra bu değerleri raporlayanlarla karşılaştırdım sys.dm_db_database_page_allocations
ve sayfa sayısı için aynı değerler değildi. Bir yığınta, toplama işlevlerini kaldırdım GROUP BY
ve SELECT
listeyi değiştirdim a.*, '---' AS [---], p.*
. Ve sonra netleşti: insanlar bu karanlık iç içe web'lerde ;-) bilgi ve senaryolarını nereden aldıklarına dikkat etmelidir. Soruda yayınlanan 2. sorgu, özellikle bu soru için tam olarak doğru değildir.
Küçük sorun: bunun dışında çok fazla mantıklı GROUP BY rows
değil (ve bu sütunu bir toplu işleve sahip değil), aradaki JOIN sys.allocation_units
ve sys.partitions
teknik olarak doğru değil. 3 tip Tahsis Birimi vardır ve bunlardan biri farklı bir alana katılmalıdır. Oldukça sık partition_id
ve hobt_id
aynıdır, bu yüzden asla bir sorun olmayabilir, ancak bazen bu iki alanın farklı değerleri vardır.
Büyük sorun: sorgu used_pages
alanı kullanır . Bu alan tüm sayfa türlerini kapsar : Veri, Dizin, IAM, vb. Tc. Sadece gerçek verilerle ilgili zaman başka, daha uygun alan kullanımına vardır: data_pages
.
Sorudaki 2. sorguyu yukarıdaki öğeleri göz önünde bulundurarak ve sayfa başlığını yedekleyen veri sayfası boyutunu kullanarak uyarladım. : Ayrıca iki o gereksiz JOIN kaldırıldı sys.schemas
(çağrısına ile değiştirilir SCHEMA_NAME()
) ve sys.indexes
(kümelenmiş dizin her zaman index_id = 1
ve sahip olduğumuz index_id
içinde sys.partitions
).
SELECT SCHEMA_NAME(st.[schema_id]) AS [SchemaName],
st.[name] AS [TableName],
SUM(sp.[rows]) AS [RowCount],
(SUM(sau.[total_pages]) * 8.0) / 1024 AS [TotalSpaceMB],
(SUM(CASE sau.[type]
WHEN 1 THEN sau.[data_pages]
ELSE (sau.[used_pages] - 1) -- back out the IAM page
END) * 7.91) / 1024 AS [TotalActualDataMB]
FROM sys.tables st
INNER JOIN sys.partitions sp
ON sp.[object_id] = st.[object_id]
INNER JOIN sys.allocation_units sau
ON ( sau.[type] = 1
AND sau.[container_id] = sp.[partition_id]) -- IN_ROW_DATA
OR ( sau.[type] = 2
AND sau.[container_id] = sp.[hobt_id]) -- LOB_DATA
OR ( sau.[type] = 3
AND sau.[container_id] = sp.[partition_id]) -- ROW_OVERFLOW_DATA
WHERE st.is_ms_shipped = 0
--AND sp.[object_id] = OBJECT_ID(N'dbo.table_name')
AND sp.[index_id] < 2 -- 1 = Clustered Index; 0 = Heap
GROUP BY SCHEMA_NAME(st.[schema_id]), st.[name]
ORDER BY [TotalSpaceMB] DESC;