SELECT'leri engelleyen büyük INSERT'ler


14

SELECT işlemlerimi engelleyen büyük miktarda INSERT ile ilgili bir sorunum var.

Şema

Ben böyle bir tablo var:

CREATE TABLE [InverterData](
    [InverterID] [bigint] NOT NULL,
    [TimeStamp] [datetime] NOT NULL,    
    [ValueA] [decimal](18, 2) NULL,
    [ValueB] [decimal](18, 2) NULL
    CONSTRAINT [PrimaryKey_e149e28f-5754-4229-be01-65fafeebce16] PRIMARY KEY CLUSTERED 
    (
        [TimeStamp] DESC,
        [InverterID] ASC
    ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF
    , IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON
    , ALLOW_PAGE_LOCKS = ON)
)

Ayrıca MERGE komutuyla eklemem veya güncellememi (çakışmada güncelleme) sağlayan bu küçük yardımcı yordam var:

CREATE PROCEDURE [InsertOrUpdateInverterData]
    @InverterID bigint, @TimeStamp datetime
    , @ValueA decimal(18,2), @ValueB decimal(18,2)
AS
BEGIN
    MERGE [InverterData] AS TARGET
        USING (VALUES (@InverterID, @TimeStamp, @ValueA, @ValueB))
        AS SOURCE ([InverterID], [TimeStamp], [ValueA], [ValueB])
        ON TARGET.[InverterID] = @InverterID AND TARGET.[TimeStamp] = @TimeStamp
    WHEN MATCHED THEN
        UPDATE
        SET [ValueA] = SOURCE.[ValueA], [ValueB] = SOURCE.[ValueB]              
    WHEN NOT MATCHED THEN
        INSERT ([InverterID], [TimeStamp], [ValueA], [ValueB]) 
        VALUES (SOURCE.[InverterID], SOURCE.[TimeStamp], SOURCE.[ValueA], SOURCE.[ValueB]);
END

kullanım

Şimdi [InsertOrUpdateInverterData]yordamı hızla çağırarak büyük güncelleştirmeleri gerçekleştiren birden çok sunucuda hizmet örnekleri çalıştırdım .

[InverterData]Tabloda SELECT sorguları yapan bir web sitesi de var .

Sorun

[InverterData]Tabloda SELECT sorguları yaparsam, bunlar servis örneklerimin INSERT kullanımına bağlı olarak farklı zaman aralıklarında ilerler. Tüm hizmet örneklerini duraklatırsam SELECT yıldırım hızındadır, örnek hızlı ekleme gerçekleştirirse SELECT'ler gerçekten yavaşlar, hatta bir zaman aşımı iptali olur.

Denemeler

[sys.dm_tran_locks]Bu gibi kilitleme işlemlerini bulmak için bazı SELECT'ler yaptım

SELECT
tl.request_session_id,
wt.blocking_session_id,
OBJECT_NAME(p.OBJECT_ID) BlockedObjectName,
h1.TEXT AS RequestingText,
h2.TEXT AS BlockingText,
tl.request_mode

FROM sys.dm_tran_locks AS tl

INNER JOIN sys.dm_os_waiting_tasks AS wt ON tl.lock_owner_address = wt.resource_address
INNER JOIN sys.partitions AS p ON p.hobt_id = tl.resource_associated_entity_id
INNER JOIN sys.dm_exec_connections ec1 ON ec1.session_id = tl.request_session_id
INNER JOIN sys.dm_exec_connections ec2 ON ec2.session_id = wt.blocking_session_id
CROSS APPLY sys.dm_exec_sql_text(ec1.most_recent_sql_handle) AS h1
CROSS APPLY sys.dm_exec_sql_text(ec2.most_recent_sql_handle) AS h2

Sonuç budur:

resim açıklamasını buraya girin

S = Paylaşıldı. Bekletme oturumuna kaynağa paylaşılan erişim izni verilir.

Soru

SELECT'ler neden [InsertOrUpdateInverterData]yalnızca MERGE komutlarını kullanan yordam tarafından engelleniyor ?

İçinde tanımlı izolasyon moduyla bir tür işlem kullanmak zorunda [InsertOrUpdateInverterData]mıyım?

Güncelleme 1 (@Paul'dan gelen soru ile ilgili)

[InsertOrUpdateInverterData]Aşağıdaki istatistiklerle ilgili MS-SQL sunucusu dahili raporlamasına dayanır:

  • Ortalama CPU Zamanı: 0.12ms
  • Ortalama Okuma işlemleri: 5,76 / s
  • Ortalama Yazma işlemleri: 0,4 / s

Buna dayanarak, MERGE komutu çoğunlukla tabloyu kilitleyecek okuma işlemleriyle meşgul gibi görünüyor! (?)

Güncelleme 2 (@Paul'dan gelen soru ile ilgili)

Aşağıdaki [InverterData]tablo istatistiklerine sahiptir:

  • Veri alanı: 26.901.86 MB
  • Sıra sayısı: 131,827,749
  • Bölümlenmiş: true
  • Bölüm sayısı: 62

İşte (allmost) tam sp_WhoIsActive sonuç kümesi:

SELECT komuta

  • gg ss: dd: ss.mss: 00 00: 01: 01.930
  • oturum_kimliği: 73
  • wait_info: (12629ms) LCK_M_S
  • İşlemci: 198
  • blocking_session_id: 146
  • okur: 99.368
  • yazar: 0
  • durum: askıya alındı
  • open_tran_count: 0

Engelleme [InsertOrUpdateInverterData]komutu

  • gg ss: dd: ss.mss: 00 00: 00: 00.330
  • oturum_kimliği: 146
  • wait_info: NULL
  • İşlemci: 3.972
  • blocking_session_id: NULL
  • okur: 376,95
  • yazar: 126
  • durumu: uyku
  • open_tran_count: 1

([TimeStamp] DESC, [InverterID] ASC)Bakışlar kümelenmiş dizin için garip bir seçim gibi. DESCKısım demek istiyorum .
ypercubeᵀᴹ

Anlıyorum: Kümelenmiş dizin DESC veri ekleme tablo sonuna yeniden eklemek zorlayacak ... performans köpek; yeniden oluşturma sırasında masayı kilitlerdi ... evet. Jove tarafından var. Yapı, kilitlerden daha fazla kilitlemenin nedenidir.
Alocyte

Yanıtlar:


12

İlk olarak, ana soru ile biraz ilgisi olmasa da, ifadeniz MERGEpotansiyel olarak bir yarış durumu nedeniyle hata riski altındadır . Özetle sorun, birden fazla eşzamanlı iş parçacığının hedef satırın var olmadığı sonucuna varması ve çarpışma girişimleriyle sonuçlanması mümkün olmasıdır. Temel neden, var olmayan bir satırda paylaşılan veya güncelleme kilidi almanın mümkün olmamasıdır. Çözüm bir ipucu eklemektir:

MERGE [dbo].[InverterData] WITH (SERIALIZABLE) AS [TARGET]

Seri hale getirilebilir yalıtım düzeyi ipucu gider satır kilitli anahtar aralığını sağlar. Aralık kilidini desteklemek için benzersiz bir endeksiniz var, bu nedenle bu ipucunun kilitleme üzerinde olumsuz bir etkisi olmayacak, bu potansiyel yarış durumuna karşı sadece koruma kazanacaksınız.

Ana Soru

Neden SELECTsyalnızca MERGEkomutları kullanan [InsertOrUpdateInverterData] yordamı tarafından engellendi ?

Varsayılan kilitleme okuma taahhütlü izolasyon seviyesi altında, veri okunurken paylaşılan (S) kilitler alınır ve genellikle (her zaman olmasa da) okuma tamamlandıktan hemen sonra serbest bırakılır. Bazı paylaşılan kilitler ifadenin sonuna kadar tutulur.

Bir MERGEifade verileri değiştirir, böylece değiştirilecek verileri bulurken S veya güncelleme (U) kilitlerini alır ve bunlar gerçek değişikliği gerçekleştirmeden önce özel (X) kilitlere dönüştürülür. Hem U hem de X kilitleri işlemin sonuna kadar tutulmalıdır.

Bu, 'iyimser' anlık görüntü izolasyonu (SI) hariç tüm izolasyon seviyeleri için geçerlidir - okunan sürümlendirme ile karıştırılmamalıdır, ayrıca okunan anlık görüntü izolasyonu (RCSI) olarak da bilinir .

Sorunuzdaki hiçbir şey, S kilidinin U kilidi tutan bir oturum tarafından engellenmesini bekleyen bir oturumu göstermez. Bu kilitler uyumludur . Herhangi bir engelleme neredeyse kesinlikle tutulan bir X kilidi üzerindeki engellemeden kaynaklanır. Kısa bir süre içinde çok sayıda kısa süreli kilit alınırken, dönüştürüldüğünde ve serbest bırakıldığında bu yakalamak biraz zor olabilir.

open_tran_count: 1InsertOrUpdateInverterData komuta araştırmaya değer olduğunu. Komut çok uzun süre çalışmamış olsa da, gereksiz uzun bir kapsayıcı işlem (uygulamada veya daha üst düzey saklı yordamda) olmadığından emin olmalısınız. En iyi uygulama işlemleri mümkün olduğunca kısa tutmaktır. Bu hiçbir şey olmayabilir, ama kesinlikle kontrol etmelisiniz.

Potansiyel çözüm

Kin'nin bir yorumda önerdiği gibi , bu veritabanında satır sürümleme yalıtım düzeyini (RCSI veya SI) etkinleştirmeyi düşünebilirsiniz . RCSI en sık kullanılanıdır, çünkü tipik olarak çok fazla uygulama değişikliği gerektirmez. Etkinleştirildiğinde, varsayılan okuma taahhütlü yalıtım seviyesi, okumalar için S kilitlerini almak yerine satır sürümlerini kullanır, böylece SX engelleme azaltılır veya ortadan kaldırılır. Bazı işlemler (örn. Yabancı anahtar kontrolleri) RCSI altında hala S kilitleri alır.

Satır sürümlerinin, değişim etkinliği oranı ve işlemlerin uzunluğu ile orantılı olarak geniş bir şekilde konuşarak tempdb alanı tükettiğini unutmayın. Durumunuzdaki RCSI (veya SI) etkisini anlamak ve planlamak için uygulamanızı yük altında kapsamlı bir şekilde test etmeniz gerekecektir .

Sürüm oluşturma kullanımınızı tüm işyükü için etkinleştirmek yerine yerelleştirmek istiyorsanız, SI yine de daha iyi bir seçim olabilir. Okuma işlemleri için SI'yı kullanarak, okuyucular ve yazarlar arasındaki çekişmeyi, eşzamanlı bir değişiklik başlamadan önce satırın sürümünü görme pahasına göreceksiniz (daha doğru bir şekilde, SI altındaki okuma işlemi her zaman SI işleminin başladığı sıradaki satır). Yazma işlemleri için SI kullanmanın çok az ya da hiç faydası yoktur, çünkü yazma kilitleri hala alınacaktır ve herhangi bir yazma çakışmasını ele almanız gerekecektir. İstediğiniz bu olmadığı sürece :)

Not: RCSI'nin aksine (bir kez etkinleştirildiğinde okuma işlemi tamamlanan tüm işlemler için geçerlidir), SI kullanılarak açıkça istenmesi gerekir SET TRANSACTION ISOLATION SNAPSHOT;.

İnce davranışlar bağlı yazarlar engelleme okuyucuları (tetikleyici kodunda dahil!) Olmazsa olmaz olan test olun. Ayrıntılar için bağlantılı makale serime ve Çevrimiçi Kitaplar'a bakın. RCSI'ye karar verirseniz, özellikle Taahhüt Edilen Anlık Görüntü İzolasyonu altındaki Veri Değişikliklerini gözden geçirdiğinizden emin olun .

Son olarak, örneğinizin SQL Server 2008 Service Pack 4 ile düzeltildiğinden emin olmalısınız.


0

Alçakgönüllü olarak, birleştirme kullanmazdım. IF Exists (UPDATE) ELSE (INSERT) ile devam ediyorum - satırları tanımlamak için kullandığınız iki sütunu içeren kümelenmiş bir anahtarınız var, bu yüzden kolay bir test.

MASSIVE kesici uçlarından bahsediyorsunuz ve yine de 1'e 1 ... bir hazırlama tablosundaki verileri topluyor ve bir seferde 1'den fazla güncelleme / ekleme yapmak için POWER OVERWHELMING SQL veri seti gücünü kullanmayı düşünüyor musunuz? Aşama tablosundaki içerik için rutin bir test yapmak ve bir seferde 1 yerine bir seferde en iyi 10000'i yakalamak gibi ...

Güncellememde böyle bir şey yapardım

DECLARE @Set TABLE (StagingKey, ID,DATE)
INSERT INTO @Set
UPDATE Staging 
SET InProgress = 1
OUTPUT StagingKey, Staging.ID, Staging.Date
WHERE InProgress = 0
AND StagingID IN (SELECT TOP (100000) StagingKey FROM Staging WHERE inProgress = 0 ORDER BY StagingKey ASC ) --FIFO

DECLARE @Temp 
INSERT INTO @TEMP 
UPDATE [DEST] SET Value = Staging.Value [whatever]
OUTPUT INSERTED.ID, DATE [row identifiers]
FROM [DEST] 
JOIN [STAGING]
JOIN [@SET]; 
INSERT INTO @TEMP 
INSERT [DEST] 
SELECT
OUTPUT INSERT.ID, DATE [row identifiers] 
FROM [STAGING] 
JOIN [@SET] 
LEFT JOIN [DEST]

UPDATE Staging
SET inProgress = NULL
FROM Staging 
JOIN @set
ON @Set.Key = Staging.Key
JOIN @temp
ON @temp.id = @set.ID
AND @temp.date = @set.Date

Muhtemelen güncelleme gruplarını patlatarak birden fazla iş çalıştırabilirsiniz ve bir damlama silme çalıştıran ayrı bir işe ihtiyacınız olacaktır

while exists (inProgress is null) 
delete top (100) from staging where inProgress is null 

hazırlama tablosunu temizlemek için.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.