Ana / ayrıntı tabloları arasındaki karma birleştirme çok düşük kardinalite tahmini üretir


9

Ana tabloyu ayrıntı tablosuna katarken, SQL Server 2014'ü daha büyük (ayrıntı) tablonun kardinalite tahminini birleştirme çıktısının kardinalite tahmini olarak kullanmaya nasıl teşvik edebilirim?

Örneğin, 10K ana satırları 100K ayrıntı satırlarına birleştirirken, SQL Server'ın 100K satırlarındaki birleştirmeyi tahmin etmesini istiyorum - tahmini ayrıntı satırı sayısıyla aynı. SQL Server'ın tahmincisinin her ayrıntı satırının her zaman karşılık gelen bir ana satırı olduğu gerçeğinden yararlanmasına yardımcı olmak için sorgularımı ve / veya tabloları ve / veya dizinlerimi nasıl yapılandırmalıyım? (Bu, aralarındaki birleştirmenin asla kardinalite tahminini azaltmaması gerektiği anlamına gelir.)

İşte daha fazla ayrıntı. Veritabanımız bir ana / detay çifti tablosuna VisitTargetsahiptir : her satış işlemi VisitSaleiçin bir satır ve her işlemdeki her ürün için bir satır vardır. Bu bire çok ilişkisidir: ortalama 10 VisitSale satırı için bir VisitTarget satırı.

Tablolar şöyle görünür: (Bu soru için yalnızca ilgili sütunları basitleştiriyorum)

-- "master" table
CREATE TABLE VisitTarget
(
  VisitTargetId int IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
  SaleDate date NOT NULL,
  StoreId int NOT NULL
  -- other columns omitted for clarity  
);
-- covering index for date-scoped queries
CREATE NONCLUSTERED INDEX IX_VisitTarget_SaleDate 
    ON VisitTarget (SaleDate) INCLUDE (StoreId /*, ...more columns */);

-- "detail" table
CREATE TABLE VisitSale
(
  VisitSaleId int IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
  VisitTargetId int NOT NULL,
  SaleDate date NOT NULL, -- denormalized; copied from VisitTarget
  StoreId int NOT NULL, -- denormalized; copied from VisitTarget
  ItemId int NOT NULL,
  SaleQty int NOT NULL,
  SalePrice decimal(9,2) NOT NULL
  -- other columns omitted for clarity  
);
-- covering index for date-scoped queries
CREATE NONCLUSTERED INDEX IX_VisitSale_SaleDate 
  ON VisitSale (SaleDate)
  INCLUDE (VisitTargetId, StoreId, ItemId, SaleQty, TotalSalePrice decimal(9,2) /*, ...more columns */
);
ALTER TABLE VisitSale 
  WITH CHECK ADD CONSTRAINT FK_VisitSale_VisitTargetId 
  FOREIGN KEY (VisitTargetId)
  REFERENCES VisitTarget (VisitTargetId);
ALTER TABLE VisitSale
  CHECK CONSTRAINT FK_VisitSale_VisitTargetId;

Performans nedenlerinden ötürü, en yaygın filtreleme sütunlarını (örn. SaleDate) Ana tablodan her ayrıntı tablosu satırına kopyalayarak kısmen denormalize ettik ve ardından tarih filtreli sorguları daha iyi desteklemek için her iki tabloya da kaplama dizinleri ekledik. Bu, tarih filtreli sorguları çalıştırırken G / Ç'yi azaltmak için harika çalışıyor, ancak bu yaklaşımın ana ve ayrıntı tablolarını birleştirirken kardinalite tahmin sorunlarına neden olduğunu düşünüyorum.

Bu iki tabloya katıldığımızda sorgular şöyle görünür:

SELECT vt.StoreId, vt.SomeOtherColumn, Sales = sum(vs.SalePrice*vs.SaleQty)
FROM VisitTarget vt 
    JOIN VisitSale vs on vt.VisitTargetId = vs.VisitTargetId
WHERE
    vs.SaleDate BETWEEN '20170101' and '20171231'
    and vt.SaleDate BETWEEN '20170101' and '20171231'
    -- more filtering goes here, e.g. by store, by product, etc. 

Ayrıntı tablosundaki ( VisitSale) tarih filtresi yedeklidir. Bir tarih aralığına göre filtrelenen sorgular için ayrıntı tablosunda sıralı G / Ç'yi (diğer adıyla Index Seek operatörü) etkinleştirmek için vardır.

Bu tür sorguların planı şuna benzer:

resim açıklamasını buraya girin

Aynı soruna sahip gerçek bir sorgu planı burada bulunabilir .

Gördüğünüz gibi, birleştirme için kardinalite tahmini (resimde sol alt kısımdaki araç ipucu) 4 katın üzerinde çok düşük: 2.1M gerçek ve 0.5M tahmini. Bu, özellikle bu sorgu daha karmaşık bir sorguda kullanılan bir alt sorgu olduğunda, performans sorunlarına (örneğin tempdb'ye dökülme) neden olur.

Ancak birleştirme işleminin her dalı için satır sayısı tahminleri gerçek satır sayılarına yakındır. Birleşmenin üst yarısı tahmin edilen 164K ile 100K arasında. Birleşmenin alt yarısı, tahmin edilen 3,7 milyona kıyasla 2,1 milyon satırdır. Karma kova dağıtımı da iyi görünüyor. Bu gözlemler bana istatistiklerin her tablo için uygun olduğunu ve sorunun birleştirme kardinalitesinin tahmini olduğunu gösteriyor.

İlk başta, sorunun SQL Server olduğunu, her tabloda SaleDate sütunlarının bağımsız olmasını beklerken, gerçekten aynı olduklarını düşündüm. Bu nedenle, Satış tarihleri ​​için birleştirme durumuna veya WHERE yantümcesine bir eşitlik karşılaştırması eklemeyi denedim, örn.

ON vt.VisitTargetId = vs.VisitTargetId and vt.SaleDate = vs.SaleDate

veya

WHERE vt.SaleDate = vs.SaleDate

Bu işe yaramadı. Hatta kardinalite tahminlerini daha da kötüleştirdi! SQL Server bu eşitlik ipucunu kullanmıyor ya da başka bir şey sorunun temel nedenidir.

Bu önemlilik tahmini sorununu nasıl gidereceğiniz ve umarım düzeltebileceğiniz konusunda fikirleriniz mi var? Amacım, ana / detay birleştirmenin önem derecesinin, birleştirmenin daha büyük ("ayrıntı tablosu") girdisi ile aynı tahmin edilmesidir.

Önemliyse, Windows Server'da SQL Server 2014 Enterprise SP2 CU8 derlemesi 12.0.5557.0 çalıştırıyoruz. Etkin hiçbir izleme bayrağı yok. Veritabanı uyumluluk düzeyi SQL Server 2014'tür. Aynı davranışı birden çok farklı SQL Sunucusunda görüyoruz, bu nedenle sunucuya özgü bir sorun olması pek olası görünmüyor.

SQL Server 2014 Kardinalite Tahmincisi'nde tam olarak aradığım davranış olan bir optimizasyon var :

Ancak yeni CE, büyük bir tablo ile küçük bir tablo arasında bire çok birleştirme ilişkisi olduğunu varsayan daha basit bir algoritma kullanır. Bu, büyük tablodaki her satırın küçük tablodaki tam bir satırla eşleştiğini varsayar. Bu algoritma, büyük girişin tahmini boyutunu birleştirme kardinalitesi olarak döndürür.

İdeal olarak ben "küçük" tablo hala 100 bin satır üzerinde dönecek olsa bile, katılmak için kardinalite tahmini büyük tablo için tahmin ile aynı olacağını bu davranış alabilir!

Yanıtlar:


6

Hiçbir gelişme istatistiklerine şey yapıyor veya eski CE kullanılarak elde edilebilir olduğu varsayıldığında, sonra soruna en basit yolu değiştirmektir INNER JOINa LEFT OUTER JOIN:

SELECT vt.StoreId, vt.SomeOtherColumn, Sales = sum(vs.SalePrice*vs.SaleQty)
FROM VisitSale vs
    LEFT OUTER JOIN VisitTarget vt on vt.VisitTargetId = vs.VisitTargetId
            AND vt.SaleDate BETWEEN '20170101' and '20171231'
WHERE vs.SaleDate BETWEEN '20170101' and '20171231'

Tablolar arasında bir yabancı anahtarınız varsa, her SaleDateiki tablo için de aynı aralıkta filtre uygularsınız ve tablolar arasında her zaman eşleşirseniz SaleDate, sorgunuzun sonuçları değişmemelidir. Bunun gibi bir dış birleşim kullanmak tuhaf görünebilir, ancak bunu sorgu eniyileyicisini VisitTargettabloya eklemenin sorgunun döndürdüğü satır sayısını hiçbir zaman azaltmayacağını bildirmek olarak düşünün . Ne yazık ki, yabancı anahtarlar kardinalite tahminlerini değiştirmez, bu yüzden bazen böyle numaralara başvurmanız gerekir. (Microsoft Connect önerisi: Meta verileri kullanarak optimize edici tahminlerini daha doğru hale getirin ).

Bu formda sorguyu yazmanın, birleştirme işleminden sonra sorguda başka neler olduğuna bağlı olarak iyi çalışmayabilir. En önemli kardinalite tahminiyle sonuç kümesinin ara sonuçlarını tutmak için geçici bir tablo kullanmayı deneyebilirsiniz.

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.