Saya mendapat kesan bahwa jika saya menjumlahkan DATALENGTH()
semua bidang untuk semua catatan dalam tabel, saya akan mendapatkan ukuran total tabel. Apakah saya salah?
SELECT
SUM(DATALENGTH(Field1)) +
SUM(DATALENGTH(Field2)) +
SUM(DATALENGTH(Field3)) TotalSizeInBytes
FROM SomeTable
WHERE X, Y, and Z are true
Saya menggunakan kueri ini di bawah ini (yang saya dapatkan dari online untuk mendapatkan ukuran tabel, hanya indeks berkerumun sehingga tidak termasuk indeks NC) untuk mendapatkan ukuran tabel tertentu dalam database saya. Untuk tujuan penagihan (kami menagih departemen kami dengan jumlah ruang yang mereka gunakan) Saya perlu mencari tahu berapa banyak ruang yang digunakan masing-masing departemen dalam tabel ini. Saya memiliki kueri yang mengidentifikasi setiap grup di dalam tabel. Saya hanya perlu mencari tahu berapa banyak ruang yang digunakan oleh masing-masing kelompok.
Ruang per baris dapat berayun liar karena VARCHAR(MAX)
bidang dalam tabel, jadi saya tidak bisa hanya mengambil ukuran rata-rata * rasio baris untuk suatu departemen. Ketika saya menggunakan DATALENGTH()
pendekatan yang dijelaskan di atas, saya hanya mendapatkan 85% dari total ruang yang digunakan dalam kueri di bawah ini. Pikiran?
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
Disarankan agar saya membuat indeks yang difilter untuk setiap departemen atau partisi tabel, jadi saya bisa langsung meminta ruang yang digunakan per indeks. Indeks yang difilter dapat dibuat secara programatik (dan dijatuhkan lagi selama jendela pemeliharaan atau ketika saya perlu melakukan penagihan berkala), alih-alih menggunakan spasi sepanjang waktu (partisi akan lebih baik dalam hal ini).
Saya suka saran itu dan biasanya akan melakukannya. Tapi jujur saja saya menggunakan "masing-masing dept" sebagai contoh untuk menjelaskan mengapa saya membutuhkan ini, tapi jujur, itu tidak benar-benar mengapa. Karena alasan kerahasiaan, saya tidak dapat menjelaskan alasan pasti mengapa saya membutuhkan data ini, tetapi ini analog dengan departemen yang berbeda.
Mengenai indeks nonclustered pada tabel ini: Jika saya bisa mendapatkan ukuran indeks NC, itu akan bagus. Namun, indeks NC menyumbang <1% ukuran indeks berkerumun, jadi kami tidak termasuk. Namun, bagaimana kita memasukkan indeks NC? Saya bahkan tidak bisa mendapatkan ukuran yang akurat untuk indeks Clustered :)
sumber
Jawaban:
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.
Data bukan satu-satunya hal yang menggunakan ruang pada halaman data 8k:
Ada ruang yang dipesan. Anda hanya diperbolehkan menggunakan 8060 dari 8192 byte (pertama, 132 byte yang tidak pernah menjadi milik Anda):
DBCC PAGE
, oleh karena itu disimpan terpisah di sini daripada dimasukkan dalam info per-baris di bawah ini.NULL
. 1 byte per setiap set 8 kolom. Dan untuk semua kolom, bahkan kolomNOT NULL
. Oleh karena itu, minimal 1 byte.ALLOW_SNAPSHOT_ISOLATION ON
atauREAD_COMMITTED_SNAPSHOT ON
).Pointer LOB untuk data yang tidak disimpan dalam baris. Jadi itu akan menjelaskan
DATALENGTH
+ pointer_size. Tapi ini bukan ukuran standar. Silakan lihat posting blog berikut untuk detail tentang topik kompleks ini: Berapa Ukuran Pointer LOB untuk (MAX) Jenis Seperti Varchar, Varbinary, Etc? . Di antara pos tertaut dan beberapa pengujian tambahan yang telah saya lakukan , aturan (default) adalah sebagai berikut:TEXT
,NTEXT
, danIMAGE
):text in row
opsi, maka:VARCHAR(MAX)
,NVARCHAR(MAX)
, danVARBINARY(MAX)
):large value types out of row
opsi, maka selalu gunakan pointer 16 byte ke penyimpanan LOB.Halaman overflow LOB: Jika nilainya 10k, maka itu akan membutuhkan 1 halaman penuh 8k, dan kemudian bagian dari halaman ke-2. Jika tidak ada data lain yang dapat mengambil ruang yang tersisa (atau bahkan diizinkan, saya tidak yakin akan aturan itu), maka Anda memiliki kira-kira 6kb ruang "terbuang" pada datapage LOB ke-2.
Ruang yang tidak digunakan: Halaman data 8k hanya itu: 8192 byte. Ukurannya tidak bervariasi. Data dan meta-data yang ditempatkan di atasnya, bagaimanapun, tidak selalu cocok dengan semua 8192 byte. Dan baris tidak dapat dipisah menjadi beberapa halaman data. Jadi jika Anda memiliki 100 byte yang tersisa tetapi tidak ada baris (atau tidak ada baris yang sesuai dengan lokasi itu, tergantung pada beberapa faktor) dapat masuk ke sana, halaman data masih mengambil 8192 byte, dan permintaan 2 Anda hanya menghitung jumlah halaman data. Anda dapat menemukan nilai ini di dua tempat (perlu diingat bahwa sebagian dari nilai ini adalah sejumlah ruang yang dipesan):
DBCC PAGE( db_name, file_id, page_id ) WITH TABLERESULTS;
CariParentObject
= "PAGE HEADER:" danField
= "m_freeCnt". TheValue
lapangan adalah jumlah byte yang tidak terpakai.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;
Ini adalah nilai yang sama seperti yang dilaporkan oleh "m_freeCnt". Ini lebih mudah daripada DBCC karena bisa mendapatkan banyak halaman, tetapi juga mengharuskan halaman tersebut dibaca di dalam buffer pool.Ruang dicadangkan oleh
FILLFACTOR
<100. Halaman yang baru dibuat tidak menghormatiFILLFACTOR
pengaturan, tetapi melakukan REBUILD akan mencadangkan ruang itu pada setiap halaman data. Gagasan di balik ruang yang dipesan adalah bahwa itu akan digunakan oleh sisipan non-sekuensial dan / atau pembaruan yang memperluas ukuran baris pada halaman, karena kolom panjang variabel diperbarui dengan sedikit lebih banyak data (tetapi tidak cukup untuk menyebabkan halaman-split). Tetapi Anda dapat dengan mudah memesan ruang pada halaman data yang secara alami tidak akan pernah mendapatkan baris baru dan tidak pernah memiliki baris yang diperbarui, atau setidaknya tidak diperbarui dengan cara yang akan meningkatkan ukuran baris.Page-Splits (fragmentasi): Perlu menambahkan baris ke lokasi yang tidak memiliki ruang untuk baris akan menyebabkan pemisahan halaman. Dalam hal ini, sekitar 50% dari data yang ada dipindahkan ke halaman baru dan baris baru ditambahkan ke salah satu dari 2 halaman. Tetapi sekarang Anda memiliki sedikit lebih banyak ruang kosong yang tidak diperhitungkan dengan
DATALENGTH
perhitungan.Baris yang ditandai untuk dihapus. Ketika Anda menghapus baris, mereka tidak selalu segera dihapus dari halaman data. Jika mereka tidak dapat segera dihapus, mereka "ditandai untuk mati" (referensi Steven Segal) dan kemudian akan dihapus secara fisik oleh proses pembersihan hantu (saya percaya itu adalah namanya). Namun, ini mungkin tidak relevan dengan Pertanyaan khusus ini.
Halaman hantu? Tidak yakin apakah itu istilah yang tepat, tetapi kadang-kadang halaman data tidak bisa dihapus sampai REBUILD Indeks Clustered selesai. Itu juga akan menjelaskan lebih banyak halaman daripada yang
DATALENGTH
akan ditambahkan. Ini umumnya tidak boleh terjadi, tetapi saya pernah mengalami satu kali, beberapa tahun yang lalu.Kolom SPARSE: Kolom jarang menghemat ruang (kebanyakan untuk tipe data panjang tetap) dalam tabel di mana sebagian besar baris adalah
NULL
untuk satu atau beberapa kolom. TheSPARSE
pilihan membuatNULL
jenis nilai sampai 0 bytes (bukan normal jumlah tetap-panjang, seperti 4 byte untukINT
), namun , non-NULL nilai masing-masing mengambil sebuah tambahan 4 byte untuk jenis fixed-panjang dan jumlah variabel untuk tipe panjang variabel. Masalahnya di sini adalah bahwaDATALENGTH
tidak termasuk tambahan 4 byte untuk nilai non-NULL dalam kolom SPARSE, sehingga 4 byte tersebut perlu ditambahkan kembali. Anda dapat memeriksa untuk melihat apakah adaSPARSE
kolom melalui:Dan kemudian untuk setiap
SPARSE
kolom, perbarui kueri asli untuk menggunakan:Harap perhatikan bahwa perhitungan di atas untuk menambahkan standar 4 byte agak sederhana karena hanya bekerja untuk jenis yang memiliki panjang tetap. DAN, ada meta-data tambahan per baris (dari apa yang bisa saya katakan sejauh ini) yang mengurangi ruang yang tersedia untuk data, hanya dengan memiliki setidaknya satu kolom SPARSE. Untuk detail lebih lanjut, silakan lihat halaman MSDN untuk Menggunakan Kolom Jarang .
Halaman indeks dan lainnya (mis. IAM, PFS, GAM, SGAM, dll): ini bukan halaman "data" dalam hal data pengguna. Ini akan mengembang ukuran total tabel. Jika menggunakan SQL Server 2012 atau yang lebih baru, Anda dapat menggunakan
sys.dm_db_database_page_allocations
Dynamic Management Function (DMF) untuk melihat tipe halaman (versi SQL Server sebelumnya dapat digunakanDBCC IND(0, N'dbo.table_name', 0);
):Baik
DBCC IND
maupunsys.dm_db_database_page_allocations
(dengan klausa WHERE) itu akan melaporkan halaman Indeks mana pun, dan hanyaDBCC IND
akan melaporkan setidaknya satu halaman IAM.DATA_COMPRESSION: Jika Anda memiliki
ROW
atauPAGE
Kompresi diaktifkan pada Clustered Index atau Heap, maka Anda dapat melupakan sebagian besar dari apa yang telah disebutkan sejauh ini. Header Halaman 96 byte, 2 Slot Slot byte-per-baris, dan Info Versi 14 byte-per-baris masih ada, tetapi representasi fisik data menjadi sangat kompleks (lebih dari apa yang telah disebutkan ketika Kompresi sedang tidak digunakan). Misalnya, dengan Kompresi Baris, SQL Server mencoba menggunakan wadah sekecil mungkin untuk memenuhi setiap kolom, per setiap baris. Jadi jika Anda memilikiBIGINT
kolom yang jika tidak (dengan asumsiSPARSE
juga tidak diaktifkan) selalu mengambil 8 byte, jika nilainya antara -128 dan 127 (yaitu ditandatangani bilangan bulat 8-bit) maka akan menggunakan hanya 1 byte, dan jika nilai bisa masuk ke dalamSMALLINT
, itu hanya akan memakan waktu 2 byte. Tipe integer yang salahNULL
atau tidak0
memakan ruang dan hanya ditunjukkan sebagai sedangNULL
atau "kosong" (yaitu0
) dalam array memetakan kolom. Dan ada banyak, banyak aturan lainnya. Punya data Unicode (NCHAR
,,NVARCHAR(1 - 4000)
tetapi tidakNVARCHAR(MAX)
, bahkan jika disimpan dalam baris)? Unicode Compression ditambahkan dalam SQL Server 2008 R2, tetapi tidak ada cara untuk memprediksi hasil dari nilai "terkompresi" di semua situasi tanpa melakukan kompresi aktual mengingat kompleksitas aturan .Jadi sungguh, permintaan kedua Anda, walaupun lebih akurat dalam hal total ruang fisik yang digunakan pada disk, hanya benar-benar akurat saat melakukan
REBUILD
Indeks Clustered. Dan setelah itu, Anda masih perlu memperhitungkanFILLFACTOR
pengaturan apa pun di bawah 100. Dan meskipun demikian selalu ada tajuk halaman, dan seringkali cukup sejumlah ruang "terbuang" yang tidak dapat diisi karena terlalu kecil untuk ditampung dalam baris apa pun di ini tabel, atau setidaknya baris yang secara logis harus masuk dalam slot itu.Mengenai keakuratan kueri ke-2 dalam menentukan "penggunaan data", tampaknya paling adil untuk membatalkan byte Page Header karena mereka bukan penggunaan data: itu adalah biaya overhead bisnis. Jika ada 1 baris pada halaman data dan baris itu hanya a
TINYINT
, maka 1 byte itu tetap mensyaratkan bahwa halaman data ada dan karenanya 96 byte header. Haruskah 1 departemen dikenakan biaya untuk seluruh halaman data? Jika halaman data itu kemudian diisi oleh Departemen # 2, apakah mereka akan membagi secara merata biaya "overhead" atau membayar secara proporsional? Tampaknya paling mudah untuk mundur saja. Dalam hal ini, menggunakan nilai8
untuk menggandakan melawannumber of pages
terlalu tinggi. Bagaimana tentang:Karenanya, gunakan sesuatu seperti:
untuk semua perhitungan terhadap kolom "number_of_pages".
DAN , mengingat bahwa menggunakan
DATALENGTH
per bidang masing-masing tidak dapat mengembalikan meta-data per-baris, yang harus ditambahkan ke kueri per-tabel di mana Anda mendapatkanDATALENGTH
per bidang masing-masing, memfilter pada setiap "departemen":ALLOW_SNAPSHOT_ISOLATION
atauREAD_COMMITTED_SNAPSHOT
diatur keON
)NULL
, dan jika nilainya cocok pada baris maka itu bisa jauh lebih kecil atau lebih besar dari pointer, dan jika nilainya disimpan off- baris, maka ukuran pointer mungkin tergantung pada seberapa banyak data yang ada. Namun, karena kami hanya menginginkan perkiraan (mis. "Barang curian"), sepertinya 24 byte adalah nilai yang baik untuk digunakan (well, sebagus ;-) lainnya. Ini adalah perMAX
bidang masing-masing .Karenanya, gunakan sesuatu seperti:
Secara umum (tajuk baris + jumlah kolom + susunan slot + bitmap NULL):
Secara umum (deteksi otomatis jika "info versi" ada):
JIKA ada kolom panjang variabel, lalu tambahkan:
JIKA ada
MAX
/ kolom LOB, lalu tambahkan:Secara umum:
Ini tidak tepat, dan sekali lagi tidak akan berfungsi jika Anda mengaktifkan Kompresi Baris atau Halaman pada Heap atau Clustered Index, tetapi pasti akan membuat Anda lebih dekat.
PEMBARUAN Mengenai Misteri Perbedaan 15%
Kami (termasuk saya) sangat fokus pada pemikiran tentang bagaimana halaman data disusun dan bagaimana
DATALENGTH
mungkin menjelaskan hal-hal yang kami tidak menghabiskan banyak waktu untuk meninjau permintaan ke-2. Saya menjalankan kueri itu terhadap satu tabel dan kemudian membandingkan nilai-nilai itu dengan apa yang dilaporkan olehsys.dm_db_database_page_allocations
dan mereka bukan nilai yang sama untuk jumlah halaman. Pada firasat, saya menghapus fungsi agregat danGROUP BY
, dan menggantiSELECT
daftar dengana.*, '---' AS [---], p.*
. Dan kemudian menjadi jelas: orang-orang harus berhati-hati di mana pada jalinan keruh ini mereka mendapatkan info dan skrip mereka dari ;-). Kueri ke-2 yang diposting di Pertanyaan tidak sepenuhnya benar, terutama untuk Pertanyaan khusus ini.Masalah kecil: di luarnya tidak masuk akal untuk
GROUP BY rows
(dan tidak memiliki kolom dalam fungsi agregat), GABUNG antarasys.allocation_units
dansys.partitions
secara teknis tidak benar. Ada 3 jenis Unit Alokasi, dan salah satunya harus BERGABUNG ke bidang yang berbeda. Cukup seringpartition_id
danhobt_id
sama, sehingga mungkin tidak pernah ada masalah, tetapi terkadang kedua bidang tersebut memiliki nilai yang berbeda.Masalah utama: kueri menggunakan
used_pages
bidang. Bidang itu mencakup semua jenis halaman: Data, Indeks, IAM, dll, tc. Ada, bidang lain yang lebih tepat untuk digunakan saat yang bersangkutan dengan hanya data aktual:data_pages
.Saya menyesuaikan kueri ke-2 dalam Pertanyaan dengan item di atas, dan menggunakan ukuran halaman data yang mendukung header halaman. Saya juga menghapus dua GABUNGAN yang tidak perlu:
sys.schemas
(diganti dengan ajakan untukSCHEMA_NAME()
), dansys.indexes
(Indeks Clustered selaluindex_id = 1
dan kami adaindex_id
disys.partitions
).sumber
Mungkin ini adalah jawaban grunge tetapi inilah yang akan saya lakukan.
Jadi DATALENGTH hanya menyumbang 86% dari total. Perpecahan masih sangat representatif. Overhead dalam jawaban yang sangat baik dari srutzky harus memiliki perpecahan yang cukup merata.
Saya akan menggunakan permintaan kedua Anda (halaman) untuk total. Dan gunakan yang pertama (datalength) untuk mengalokasikan split. Banyak biaya dialokasikan menggunakan normalisasi.
Dan Anda harus mempertimbangkan jawaban yang lebih dekat akan menaikkan biaya sehingga bahkan dept yang kalah dalam perpecahan masih dapat membayar lebih.
sumber