Kötü kardinalite tahmini INSERT'i minimal kayıttan diskalifiye ediyor mu?


11

İkinci INSERTifade neden birinciden ~ 5 kat daha yavaş?

Üretilen günlük verisi miktarından, ikincisinin minimum günlük kaydı için uygun olmadığını düşünüyorum. Ancak, Veri Yükleme Performansı Kılavuzu'ndaki belgeler her iki ek parçanın da minimum düzeyde günlüğe kaydedilebileceğini gösterir. Peki, minimum günlük kaydı temel performans farkı ise, neden ikinci sorgu minimum günlük kaydı için uygun olmaz? Durumu iyileştirmek için ne yapılabilir?


Sorgu 1: INSERT ... WITH (TABLOCK) kullanarak 5MM satırlar ekleme

Yığına 5MM satır ekleyen aşağıdaki sorguyu düşünün. Bu sorgu , tarafından bildirildiği şekilde işlem günlüğü verilerini yürütür 1 secondve üretir .64MBsys.dm_tran_database_transactions

CREATE TABLE dbo.minimalLoggingTest (n INT NOT NULL)
GO
INSERT INTO dbo.minimalLoggingTest WITH (TABLOCK) (n)
SELECT n
-- Any table/view/sub-query that correctly estimates that it will generate 5MM rows
FROM dbo.fiveMillionNumbers
-- Provides greater consistency on my laptop, where other processes are running
OPTION (MAXDOP 1)
GO


Sorgu # 2: Aynı verileri ekleme, ancak SQL satırların sayısını hafife alıyor

Şimdi, tamamen aynı veriler üzerinde çalışan ancak SELECTkardinalite tahmininin çok düşük olduğu bir tablodan (veya gerçek üretim durumumda birçok birleşimle karmaşık bir ifadeden) oluşan bu çok benzer sorguyu düşünün . Bu sorgu yürütülür 5.5 secondsve 461MBişlem günlüğü verisi oluşturur.

CREATE TABLE dbo.minimalLoggingTest (n INT NOT NULL)
GO
INSERT INTO dbo.minimalLoggingTest WITH (TABLOCK) (n)
SELECT n
-- Any table/view/sub-query that produces 5MM rows but SQL estimates just 1000 rows
FROM dbo.fiveMillionNumbersBadEstimate
-- Provides greater consistency on my laptop, where other processes are running
OPTION (MAXDOP 1)
GO


Komut dosyasının tamamı

Test verilerini oluşturmak ve bu senaryolardan birini yürütmek için tam komut dosyası kümesi için bu Pastebin'e bakın . SIMPLE Kurtarma modelinde bir veritabanı kullanmanız gerektiğini unutmayın .


İş bağlamı

Milyonlarca veri satırında yarı sık sık hareket ediyoruz ve bu işlemlerin hem yürütme süresi hem de disk G / Ç yükü açısından mümkün olduğunca verimli olması önemlidir. Başlangıçta bir yığın tablosu oluşturmanın ve kullanmanın INSERT...WITH (TABLOCK)bunu yapmanın iyi bir yolu olduğu izlenimi altındaydık , ancak gerçek bir üretim senaryosunda yukarıda gösterilen durumu gözlemlediğimizden (daha karmaşık sorgular olsa da, basitleştirilmiş sürüm).

Yanıtlar:


7

Neden ikinci sorgu en az günlük kaydı için uygun değil?

İkinci sorgu için en az günlük kaydı kullanılabilir , ancak motor bunu çalışma zamanında kullanmamayı seçer.

Bir vardır minimum eşik için INSERT...SELECTdökme yük optimizasyonlar kullanmamayı tercih aşağıda. Bir toplu satır kümesi işlemi kurmanın bir maliyeti vardır ve yalnızca birkaç satırı toplu olarak eklemek etkin alan kullanımı ile sonuçlanmaz.

Durumu iyileştirmek için ne yapılabilir?

SELECT INTOBu eşiğe sahip olmayan diğer birçok yöntemden (örneğin ) birini kullanın . Alternatif olarak, kaynak sorguyu, eşiğin üzerindeki tahmini satır / sayfa sayısını artırmak için bir şekilde yeniden yazabilirsiniz INSERT...SELECT.

Daha faydalı bilgiler için Geoff'un kendi cevaplarına da bakınız .


Muhtemelen ilginç bilgiler: SET STATISTICS IO hedef tablo için mantıksal okumaları rapor eder sadece toplu yükleme optimizasyonları kullanılmadığında .


5

Sorunu kendi test donanımımla yeniden oluşturabildim:

USE test;

CREATE TABLE dbo.SourceGood
(
    SourceGoodID INT NOT NULL
        CONSTRAINT PK_SourceGood
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , SomeData VARCHAR(384) NOT NULL
);

CREATE TABLE dbo.SourceBad
(
    SourceBadID INT NOT NULL
        CONSTRAINT PK_SourceBad
        PRIMARY KEY CLUSTERED
        IDENTITY(-2147483647,1)
    , SomeData VARCHAR(384) NOT NULL
);

CREATE TABLE dbo.InsertTest
(
    SourceBadID INT NOT NULL
        CONSTRAINT PK_InsertTest
        PRIMARY KEY CLUSTERED
    , SomeData VARCHAR(384) NOT NULL
);
GO

INSERT INTO dbo.SourceGood WITH (TABLOCK) (SomeData) 
SELECT TOP(5000000) o.name + o1.name + o2.name
FROM syscolumns o
    , syscolumns o1
    , syscolumns o2;
GO

ALTER DATABASE test SET AUTO_UPDATE_STATISTICS OFF;
GO

INSERT INTO dbo.SourceBad WITH (TABLOCK) (SomeData)
SELECT TOP(5000000) o.name + o1.name + o2.name
FROM syscolumns o
    , syscolumns o1
    , syscolumns o2;
GO

ALTER DATABASE test SET AUTO_UPDATE_STATISTICS ON;
GO

BEGIN TRANSACTION;

INSERT INTO dbo.InsertTest WITH (TABLOCK)
SELECT *
FROM dbo.SourceGood;

SELECT * FROM sys.dm_tran_database_transactions;

/*
database_transaction_log_record_count
472 
database_transaction_log_bytes_used
692136
*/

COMMIT TRANSACTION;


BEGIN TRANSACTION;

INSERT INTO dbo.InsertTest WITH (TABLOCK)
SELECT *
FROM dbo.SourceBad;

SELECT * FROM sys.dm_tran_database_transactions;

/*
database_transaction_log_record_count   
5000003 
database_transaction_log_bytes_used
642699256
*/

COMMIT TRANSACTION;

Bu soruyu soruyor, neden asgari düzeyde günlüğe kaydedilen işlemi çalıştırmadan önce kaynak tablolardaki istatistikleri güncelleyerek sorunu "düzeltmiyoruz"?

TRUNCATE TABLE dbo.InsertTest;
UPDATE STATISTICS dbo.SourceBad;

BEGIN TRANSACTION;

INSERT INTO dbo.InsertTest WITH (TABLOCK)
SELECT *
FROM dbo.SourceBad;

SELECT * FROM sys.dm_tran_database_transactions;

/*
database_transaction_log_record_count
472
database_transaction_log_bytes_used
692136
*/

COMMIT TRANSACTION;

2
Gerçek kodda, SELECTiçin sonuç kümesini oluşturan çok sayıda birleşim içeren karmaşık bir ifade vardır INSERT. Bu birleşimler, son tablo ekleme operatörü için kötü kardinalite tahminleri üretir (ki bu, hatalı UPDATE STATISTICSçağrı yoluyla repro komut dosyasında simüle ettim ) ve bu nedenle UPDATE STATISTICSsorunu düzeltmek için bir komut vermek kadar basit değildir . Sorguyu basitleştirmenin, Kardinalite Tahmincisi'nin daha kolay anlaşılabilmesi için iyi bir yaklaşım olabileceğini tamamen kabul ediyorum, ancak verilen karmaşık iş mantığını uygulamak bir üç değer değildir.
Geoff Patterson

Bunu test etmek için bir SQL Server 2014 örneğim yok, ancak SQL Server 2014 Yeni Kardinalite Tahmincisi sorunlarını belirleme ve Service Card 1 iyileştirme görüşmeleri, diğerlerinin yanı sıra yeni kardinalite tahmincisini etkinleştirmek için izleme bayrağı 4199'u etkinleştirme hakkında konuşuyor. Bunu denedin mi?
Max Vernon

İyi fikir, ama yardımcı olmadı. TF 4199, TF 610'u (minimum kayıt koşullarını gevşetir) ve her ikisini birlikte denedim (hey, neden olmasın?), Ancak 2. test sorgusu için değişiklik yok.
Geoff Patterson

4

Tahmini satır sayısını artırmak için kaynak sorguyu bir şekilde yeniden yazın

Paul'ün fikrini genişletmek gerekirse, gerçekten çaresizseniz bir çözüm, kesici uç için tahmini satır sayısının toplu yükleme optimizasyonları için kaliteye yetecek kadar yüksek olmasını garanti eden bir kukla tablo eklemektir. Bunun minimum günlük kaydı aldığını ve sorgu performansını artırdığını doğruladım.

-- Create a dummy table that SQL Server thinks has a million rows
CREATE TABLE dbo.emptyTableWithMillionRowEstimate (
    n INT PRIMARY KEY
)
GO
UPDATE STATISTICS dbo.emptyTableWithMillionRowEstimate
WITH ROWCOUNT = 1000000
GO

-- Concatenate this table into the final rowset:
INSERT INTO dbo.minimalLoggingTest WITH (TABLOCK) (n)
SELECT n
-- Any table/view/sub-query that correctly estimates that it will generate 5MM rows
FROM dbo.fiveMillionNumbersBadEstimate
-- Add in dummy rowset to ensure row estimate is high enough for bulk load optimization
UNION ALL
SELECT NULL FROM dbo.emptyTableWithMillionRowEstimate
OPTION (MAXDOP 1)

Son çıkarımlar

  1. SELECT...INTOMinimum günlük kaydı gerekiyorsa, bir kerelik kesici uç işlemleri için kullanın . Paul'un belirttiği gibi, bu, satır tahmininden bağımsız olarak minimum günlük kaydı sağlayacaktır
  2. Mümkün olan her durumda, sorgu optimize edicinin etkili bir şekilde sorgulayabileceği basit bir şekilde sorguları yazın. Örneğin, ara tablo üzerinde istatistiklerin oluşturulmasına izin vermek için bir sorguyu birden çok parçaya bölmek mümkün olabilir.
  3. SQL Server 2014'e erişiminiz varsa, sorgunuzda deneyin; gerçek üretim durumumda, yeni denedim ve yeni Kardinalite Tahmincisi çok daha yüksek (ve daha iyi) bir tahmin verdi; sorgu en az günlüğe kaydedildi. Ancak, SQL 2012 ve önceki sürümlerini desteklemeniz gerekiyorsa bu yardımcı olmayabilir.
  4. Eğer çaresizseniz, bunun gibi hileli çözümler uygulanabilir!

İlgili bir makale

Paul White'ın Mayıs 2019 blog yazısı INSERT ile Minimal Logging… Heap Tables'a SELECT bu bilgilerin bazılarını daha ayrıntılı olarak ele alıyor.

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.