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 VisitTarget
sahiptir : her satış işlemi VisitSale
iç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:
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!