Bu soru bu forum konusu ile ilgilidir .
SQL Server 2008 Developer Edition'ı iş istasyonumda ve "alfa kümesi" olarak adlandırdığım iki düğümlü bir Enterprise Edition sanal makine kümesinde çalıştırıyorum.
Bir varbinary (max) sütunu olan satırları silmek için geçen süre, doğrudan o sütundaki verilerin uzunluğuyla ilişkilidir. İlk başta sezgisel gelebilir, ancak araştırmadan sonra SQL Server'ın genel olarak satırları nasıl sildiğini ve bu tür verilerle nasıl başa çıktığını anladığımla çelişir.
Sorun, .NET web uygulamasında gördüğümüz silme zaman aşımı (> 30 saniye) sorunundan kaynaklanıyor, ancak bu tartışma uğruna basitleştirdim.
Bir kayıt silindiğinde, SQL Server, işlem tamamlandıktan sonra bir Hayalet Temizleme Görevi tarafından temizlenecek bir hayalet olarak işaretler ( Paul Randal'ın bloguna bakın ). Sırasıyla bir varbinary (maks) sütununda 16 KB, 4 MB ve 50 MB veri içeren üç satırı silme testinde, bunun verilerinde satır içi kısmı ve işlemde olduğunu görüyorum log.
Bana garip gelen şey, X kilitlerinin silme işlemi sırasında tüm LOB veri sayfalarına yerleştirilmesi ve sayfaların PFS'ye yerleştirilmesidir. Bunu işlem günlüğünde, DMV ( ) ile sp_lock
ve sonuçlarında görüyorum . dm_db_index_operational_stats
page_lock_count
Bu, bu sayfalar arabellek önbelleğinde değilse, iş istasyonumda ve alfa kümemizde bir G / Ç darboğazı oluşturur. Aslında, page_io_latch_wait_in_ms
aynı DMV'den neredeyse tüm silme süresi ve page_io_latch_wait_count
kilitli sayfaların sayısına karşılık gelir. İş istasyonumdaki 50 MB dosya için bu, boş bir arabellek önbelleği ( checkpoint
/ dbcc dropcleanbuffers
) ile başlarken 3 saniyeden fazla bir süreye dönüşür ve şüphesiz ki ağır parçalanma ve yük altında daha uzun olur.
Sadece o zaman alan önbellekte yer ayırmak değildi emin olmak için çalıştı. checkpoint
SQL Server işlemine ayrılan yöntem yerine silme yürütülmeden önce diğer satırlardan 2 GB veri okumak . SQL Server'ın verileri nasıl karıştırdığını bilmediğimden, geçerli bir test olup olmadığından emin değilim. Her zaman eskiyi yeninin lehine çıkaracağını varsaydım.
Ayrıca, sayfaları bile değiştirmez. Bunu görebiliyorum dm_os_buffer_descriptors
. Silme işleminden sonra sayfalar temizken, değiştirilen sayfa sayısı üç küçük, orta ve büyük silme işleminin tümü için 20'den azdır. Ayrıca DBCC PAGE
, aranan sayfaların örneklemesinin çıktısını da karşılaştırdım ve değişiklik olmadı (sadece ALLOCATED
bit PFS'den kaldırıldı). Sadece onları yeniden konumlandırır.
Sayfa arama / deallocations soruna neden olduğunu kanıtlamak için, vanilya varbinary (max) yerine bir filestream sütun kullanarak aynı testi denedim. Silmeler LOB boyutundan bağımsız olarak sabit bir süreydi.
İlk olarak akademik sorularım:
- SQL Server'ın X'i kilitlemek için neden tüm LOB veri sayfalarını araması gerekiyor? Bu, kilitlerin bellekte nasıl temsil edildiğinin sadece bir detayı mı (sayfa ile bir şekilde saklanıyor)? Bu, G / Ç etkisini tamamen önbelleğe alınmazsa veri boyutuna büyük ölçüde bağımlı hale getirir.
- Neden X sadece onları yeniden konumlandırmak için kilitleniyor? Yeniden yerleştirme sayfaların kendilerinin değiştirilmesine gerek olmadığından, yalnızca dizin yapısını satır içi bölümle kilitlemek yeterli değil mi? Kilidin korunduğu LOB verilerine ulaşmanın başka bir yolu var mı?
- Zaten bu tür bir çalışmaya adanmış bir arka plan görevi olduğu için, sayfaları neden önceden dağıtmalıyız?
Ve belki daha da önemlisi, pratik sorum:
- Silme işlemlerinin farklı çalışmasını sağlamanın bir yolu var mı? Amacım, gerçekliğin ardından arka planda herhangi bir temizlemenin gerçekleştiği, filestream'e benzer boyuttan bağımsız olarak sabit sürelerin silinmesidir. Bu bir yapılandırma işi mi? Bir şeyleri tuhaf mı saklıyorum?
Açıklanan sınamanın nasıl yeniden oluşturulacağı (SSMS sorgu penceresinden yürütülür):
CREATE TABLE [T] (
[ID] [uniqueidentifier] NOT NULL PRIMARY KEY,
[Data] [varbinary](max) NULL
)
DECLARE @SmallID uniqueidentifier
DECLARE @MediumID uniqueidentifier
DECLARE @LargeID uniqueidentifier
SELECT @SmallID = NEWID(), @MediumID = NEWID(), @LargeID = NEWID()
-- May want to keep these IDs somewhere so you can use them in the deletes without var declaration
INSERT INTO [T] VALUES (@SmallID, CAST(REPLICATE(CAST('a' AS varchar(max)), 16 * 1024) AS varbinary(max)))
INSERT INTO [T] VALUES (@MediumID, CAST(REPLICATE(CAST('a' AS varchar(max)), 4 * 1024 * 1024) AS varbinary(max)))
INSERT INTO [T] VALUES (@LargeID, CAST(REPLICATE(CAST('a' AS varchar(max)), 50 * 1024 * 1024) AS varbinary(max)))
-- Do this before test
CHECKPOINT
DBCC DROPCLEANBUFFERS
BEGIN TRAN
-- Do one of these deletes to measure results or profile
DELETE FROM [T] WHERE ID = @SmallID
DELETE FROM [T] WHERE ID = @MediumID
DELETE FROM [T] WHERE ID = @LargeID
-- Do this after test
ROLLBACK
İşte benim iş istasyonumdaki silme işlemlerinin bazı sonuçları:
| Sütun Türü | Boyutu Sil | Süresi (ms) | Okur | Yazarlar | CPU | -------------------------------------------------- ------------------ | VarBinary | 16 KB | 40 | 13 | 2 | 0 | | VarBinary | 4 MB | 952 | 2318 | 2 | 0 | | VarBinary | 50 MB | 2976 | 28594 | 1 | 62 | -------------------------------------------------- ------------------ | Dosya Akışı | 16 KB | 1 | 12 | 1 | 0 | | Dosya Akışı | 4 MB | 0 | 9 | 0 | 0 | | Dosya Akışı | 50 MB | 1 | 9 | 0 | 0 |
Bunun yerine sadece filestream'i kullanamayız çünkü:
- Veri boyutu dağıtımımız bunu garanti etmez.
- Pratikte, birçok parçaya veri ekliyoruz ve filestream kısmi güncellemeleri desteklemiyor. Bunun etrafında tasarım yapmamız gerekecekti.
Güncelleme 1
Verilerin silme işleminin bir parçası olarak işlem günlüğüne yazıldığına dair bir teori test edildi ve durum böyle görünmüyor. Bunu yanlış mı test ediyorum? Aşağıya bakınız.
SELECT MAX([Current LSN]) FROM fn_dblog(NULL, NULL)
--0000002f:000001d9:0001
BEGIN TRAN
DELETE FROM [T] WHERE ID = @ID
SELECT
SUM(
DATALENGTH([RowLog Contents 0]) +
DATALENGTH([RowLog Contents 1]) +
DATALENGTH([RowLog Contents 3]) +
DATALENGTH([RowLog Contents 4])
) [RowLog Contents Total],
SUM(
DATALENGTH([Log Record])
) [Log Record Total]
FROM fn_dblog(NULL, NULL)
WHERE [Current LSN] > '0000002f:000001d9:0001'
5 MB'tan büyük bir dosya için bu geri döndü 1651 | 171860
.
Ayrıca, veri günlüğe yazıldıysa sayfaların kendilerinin kirli olmasını beklerim. Silme işleminden sonra kirli olanla eşleşen yalnızca serbest bırakmalar günlüğe kaydedilir.
Güncelleme 2
Paul Randal'dan bir cevap aldım. Ağacın üzerinden geçmek ve hangi sayfaların dağıtılacağını bulmak için tüm sayfaları okuması gerektiğini doğruladı ve hangi sayfalara bakmanın başka bir yolu olmadığını belirtti. Bu, 1 ve 2'ye yarım yanıttır (ancak sıra dışı verilerin kilitlenmesi ihtiyacını açıklamaz, ancak bu küçük patateslerdir).
Soru 3 hala açık: Silme işlemleri için temizleme işlemi yapmak üzere zaten bir arka plan görevi varsa, neden sayfaları neden yeniden dağıtmalısınız?
Ve elbette, tüm önemli soru: Bu boyuta bağlı silme davranışını doğrudan azaltmanın (yani, geçici olarak çalışmanın) bir yolu var mı? SQL Server'da 50 MB satırlarını depolayan ve silen tek kişi olmadıkça, bunun daha yaygın bir sorun olacağını düşünürdüm? Dışarıdaki herkes bunun etrafında bir çeşit çöp toplama işi ile çalışıyor mu?