Seçeneklerin iyi olması doğru, çünkü bu tekniğin diğerleriyle nasıl karşılaştırılacağını görmek için biraz farklı bir yaklaşım izledim.
Test
Neden sadece çeşitli yöntemlerin birbirleriyle nasıl istiflendiğine bakarak başlamıyoruz. Üç takım test yaptım:
- İlk sette DB değişiklik yapılmadı
- İkinci set,
TransactionDate
karşı gelen sorguları desteklemek için bir endeks oluşturulduktan sonra koştu Production.TransactionHistory
.
- Üçüncü set, biraz farklı bir varsayımda bulundu. Üç testin tümü aynı Ürün listesine karşı çıktığından, bu listeyi önbelleğe alırsak ne olur? Benim yöntemim bellek içi bir önbellek, diğer yöntemler ise eşdeğer bir geçici tablo kullanıyordu. İkinci test grubu için oluşturulan destek endeksi bu test grubu için hala mevcuttur.
Ek test detayları:
- Testler
AdventureWorks2012
, SQL Server 2012, SP2'de (Geliştirici Sürümü) yapıldı.
- Her test için sorgusunu kimin yanıtını aldığımı ve hangi sorgunun olduğunu etiketledim.
- Query Options'ın "Yürütmeden sonra sonuçları sil" seçeneğini kullandım. Sonuçlar.
- Lütfen ilk iki test kümesi
RowCounts
için benim yöntemim için "kapalı" göründüğünü unutmayın . Bunun sebebi benim yöntemimin ne CROSS APPLY
yaptığının manuel bir uygulaması olması : İlk sorguyu karşı çalıştırır Production.Product
ve daha sonra karşı sorgularda kullandığı 161 satır geri alır Production.TransactionHistory
. Bu nedenle, RowCount
girişlerimin değerleri her zaman diğer girişlerden 161 daha fazladır. Üçüncü test setinde (önbellekleme ile) satır sayıları tüm yöntemler için aynıdır.
- Yürütme planlarına güvenmek yerine istatistikleri yakalamak için SQL Server Profilcisi kullandım. Aaron ve Mikael zaten sorgularının planlarını gösteren harika bir iş çıkardılar ve bu bilgiyi tekrarlamaya gerek yoktu. Ve yöntemimin amacı, sorguları gerçekten önemli olmayacağı kadar basit bir forma indirmektir. Profiler'ı kullanmak için ek bir neden var, ancak daha sonra bahsedilecek.
- Yapıyı kullanmak yerine, kullanmayı
Name >= N'M' AND Name < N'S'
seçtim Name LIKE N'[M-R]%'
ve SQL Server onlara aynı şekilde davranıyor.
Sonuçlar
Destekleyici Dizin Yok
Bu, esasen kullanıma hazır AdventureWorks2012'dir. Her durumda benim yöntemim açıkça diğerlerinden daha iyi, ancak hiçbir zaman ilk 1 veya 2 yöntem kadar iyi değil.
Test 1
Aaron's CTE açıkça burada kazanmıştır.
Test 2
Aaron'un CTE (tekrar) ve Mikael'in ikinci apply row_number()
yöntemi yakın bir ikinci.
Test 3
Aaron's CTE (yine) kazanır.
Sonuç
Destekleyici bir indeks bulunmadığında TransactionDate
, yöntemim standart yapmaktan daha iyidir CROSS APPLY
, ancak yine de, CTE yöntemini kullanmak açık bir yoldur.
Destek Endeksli (Önbellek Yok)
Bu testler TransactionHistory.TransactionDate
için, bu alandaki tüm sorguların sıralanmasından bu yana bariz indeksi ekledim . "Açık" diyorum, çünkü çoğu cevap bu noktada hemfikir. Sorguların tümü en son tarihleri istediği için, TransactionDate
alan sipariş edilmeli DESC
, bu yüzden CREATE INDEX
Mikael'in cevabının altındaki ifadeyi aldım ve açık bir şekilde şunu ekledim FILLFACTOR
:
CREATE INDEX [IX_TransactionHistoryX]
ON Production.TransactionHistory (ProductID ASC, TransactionDate DESC)
WITH (FILLFACTOR = 100);
Bu dizin bir kez yerleştiğinde, sonuçlar biraz değişir.
Test 1
Bu kez, en azından Mantıksal Okumalar açısından öne çıkan yöntemim. Daha CROSS APPLY
önce Test 1 için en kötü performans gösteren yöntem, Süre kazanır ve hatta Mantıksal Okumalar'daki CTE yöntemini yener.
Test 2
Bu kez Mikael'in Reads'e apply row_number()
bakarken kazanan ilk yöntem olduğu, oysa daha önce en kötü performans gösterenlerden biriydi. Ve şimdi yöntemim, Reads'a bakarken çok yakın bir yerde geliyor. Aslında, CTE yönteminin dışında kalanların hepsi Okumalar açısından oldukça yakındır.
Test 3
Burada CTE hala kazanıyor, ancak şimdi diğer yöntemler arasındaki fark, endeksi oluşturmadan önce varolan şiddetli farkla karşılaştırıldığında zar zor farkediliyor.
Sonuç
Benim yöntemimin uygulanabilirliği şimdi daha belirgindir, ancak yerinde uygun indekslerin bulunmaması daha az dirençlidir.
Destek Dizini ve Önbelleğe Alma ile
Bu testler için önbelleklemeyi kullandım, çünkü neden olmasın ki? Benim yöntemim, diğer yöntemlerin erişemediği bellek içi önbelleklemeyi kullanmaya izin veriyor. Dürüst olmak gerekirse, Product.Product
üç testin tamamında bu diğer yöntemlerde tüm referansların yerine kullanılan aşağıdaki geçici tabloyu oluşturdum . DaysToManufacture
Alan sadece Testi Number 2'de kullanılan, ancak SQL komut dosyaları aynı tabloyu kullanmak tutarlı olmak daha kolaydı ve orada ona sahip olmak incitmedi edilir.
CREATE TABLE #Products
(
ProductID INT NOT NULL PRIMARY KEY,
Name NVARCHAR(50) NOT NULL,
DaysToManufacture INT NOT NULL
);
INSERT INTO #Products (ProductID, Name, DaysToManufacture)
SELECT p.ProductID, p.Name, p.DaysToManufacture
FROM Production.Product p
WHERE p.Name >= N'M' AND p.Name < N'S'
AND EXISTS (
SELECT *
FROM Production.TransactionHistory th
WHERE th.ProductID = p.ProductID
);
ALTER TABLE #Products REBUILD WITH (FILLFACTOR = 100);
Sınama 1
Tüm yöntemler önbelleğe alma işleminden eşit ölçüde yarar görüyor ve benim yöntemim hala ön plana çıkıyor.
Test 2
Burada, yöntemim henüz öne çıkmadığında, dizilişe göre bir fark görüyoruz, sadece 2 Mikael'in ilk apply row_number()
yönteminden daha iyi , oysa önbellek olmadan yöntemim 4 Okuyucunun arkasında kaldı.
Test 3
Lütfen aşağıya doğru güncellemeye bakınız (satırın altında) . Burada yine bazı farklılıklar görüyoruz. Yöntemimin "parametreleştirilmiş" lezzeti, şimdi Aaron'un CROSS APPLY yöntemine kıyasla 2 Okuma ile önderlik ediyor (önbellekleme olmadan eşit değildi). Ancak gerçekten garip olan şey, ilk defa önbellekten olumsuz etkilenen bir yöntem görmemiz: Aaron'un CTE yöntemi (önceden Test Numarası 3 için en iyisiydi). Ancak, ödenmediği durumlarda kredi almayacağım ve önbelleğe alma Aaron'ın CTE yöntemi hala benim yöntemim burada önbelleğe alma ile daha hızlı olduğundan, bu özel durum için en iyi yaklaşım Aaron CTE yöntemi gibi görünüyor.
Sonuç Lütfen aşağıdan yukarıya doğru (satırın altındaki) güncellemeye bakın.
İkincil bir sorgunun sonuçlarını tekrarlanan şekilde kullanan durumlar, genellikle bu sonuçları önbelleğe almaktan yararlanabilir. Ancak, önbelleğe alma bir avantaj olduğunda, söz konusu önbelleğe alma için belleği kullanmak, geçici tabloları kullanmaktan bazı avantajlar sağlar.
Yöntem
genellikle
"Header" sorgusunu (yani ProductID
s'yi ve bir durumda da, belirli harflerle başlayanları DaysToManufacture
temel alarak Name
) "detay" sorgularından (yani TransactionID
s ve s'lerini almak TransactionDate
) ayırdım. Buradaki konsept çok basit sorgular yapmaktı ve optimizer’in KATILDIĞINDA kafasını karıştırmasına izin vermemek. Açıkçası, bu her zaman avantajlı değildir , çünkü optimize ediciyi optimizasyondan da devre dışı bırakır. Ancak sonuçlarda gördüğümüz gibi, sorgu türüne bağlı olarak, bu yöntemin yararları vardır.
Bu yöntemin çeşitli tatları arasındaki fark:
Sabitler: Değiştirilebilir değerleri, parametre olmak yerine satır içi sabitler olarak gönderin. Bu, ProductID
üç testin hepsinde ve aynı zamanda " DaysToManufacture
Ürün özelliğinin beş katı" işlevi olarak Test 2'ye döndürülecek satır sayısını ifade eder . Bu alt yöntem, her birinin ProductID
veri dağıtımında geniş bir değişiklik olması durumunda faydalı olabilecek kendi yürütme planına sahip olacağı anlamına gelir ProductID
. Ancak veri dağılımında çok az değişiklik olursa, ek planlar oluşturma maliyeti buna değmeyecektir.
Parametreli: En az Gönder ProductID
olarak @ProductID
yürütme planı önbelleğe alma ve yeniden kullanım için izin. Test 2 için parametre olarak döndürülecek değişken satır sayısını da tedavi etmek için ek bir test seçeneği vardır.
Bilinmeyen Optimize: başvururken ProductID
olarak @ProductID
, veri dağıtım geniş varyasyon varsa o zaman diğer olumsuz etkisi vardır bir plan önbelleğe mümkündür ProductID
Sorgu İpucu kullanarak herhangi yardımcı olup olmadığını bilmek iyi olurdu böylece değerler.
Önbellek Ürünleri:Production.Product
Tabloyu her seferinde sorgulamak yerine , yalnızca tam olarak aynı listeyi elde etmek için sorguyu bir kez çalıştırın (ve bizdeyken ProductID
, TransactionHistory
masada bile olmayan herhangi bir öğeyi filtreleyin, böylece hiçbirini israf etmeyin) kaynakları var) ve bu listeyi önbelleğe al. Liste DaysToManufacture
alanı içermelidir . Bu seçeneği kullanarak, ilk yürütme için Mantıksal Okumalara biraz daha yüksek bir başlangıç vuruşu gelir, ancak bundan sonra yalnızca TransactionHistory
sorgulanan tablo olur.
özellikle
Tamam, ama öyleyse, tüm alt sorguları CURSOR kullanmadan ve her bir sonuç kümesini geçici tabloya veya tablo değişkenine dökmeden ayrı sorgular olarak düzenlemek nasıl mümkün olabilir? Açıkça CURSOR / Temp Table yönteminin yapılması, Açıkça Okunanlar ve Yazma bölümlerine oldukça açık bir şekilde yansıyacaktır. Peki, SQLCLR :) kullanarak. Bir SQLCLR saklı yordamı oluşturarak, bir sonuç kümesini açabildim ve esas olarak her bir alt sorgunun sonuçlarını sürekli bir sonuç kümesi (ve birden fazla sonuç kümesi olarak değil) olarak yayınladım. Ürün bilgilerinin dışında (yani ProductID
, Name
veDaysToManufacture
), alt sorgu sonuçlarından hiçbiri herhangi bir yerde (bellek veya disk) saklanmak zorunda değildi ve SQLCLR saklı yordamının ana sonuç kümesi olarak geçti. Bu, Ürün bilgisini almak için basit bir sorgu yapmamı ve daha sonra da sorgulama yapmamı sağladı TransactionHistory
.
Ve bu yüzden istatistikleri yakalamak için SQL Server Profiler kullanmak zorunda kaldım. SQLCLR saklı yordamı, "Gerçek Yürütme Planını Dahil Et" Sorgu Seçeneği ayarlayarak veya yayınlayarak bir yürütme planı döndürmedi SET STATISTICS XML ON;
.
Ürün Bilgisi önbelleğe almak için, readonly static
Genel Bir Liste kullandım (örneğin _GlobalProducts
, aşağıdaki kodda). Koleksiyonlarına eklemek ihlal etmediğini görünüyor readonly
montaj bir sahip olduğunda dolayısıyla bu kod çalışır seçeneği PERMISSON_SET
ait SAFE
olduğunu sezgilere bile :).
Üretilen Sorgular
Bu SQLCLR saklı yordam tarafından üretilen sorgular aşağıdaki gibidir:
Ürün bilgisi
Test Numaraları 1 ve 3 (Önbellekleme Yok)
SELECT prod1.ProductID, prod1.Name, 1 AS [DaysToManufacture]
FROM Production.Product prod1
WHERE prod1.Name LIKE N'[M-R]%';
Test Numarası 2 (Önbellek Yok)
;WITH cte AS
(
SELECT prod1.ProductID
FROM Production.Product prod1 WITH (INDEX(AK_Product_Name))
WHERE prod1.Name LIKE N'[M-R]%'
)
SELECT prod2.ProductID, prod2.Name, prod2.DaysToManufacture
FROM Production.Product prod2
INNER JOIN cte
ON cte.ProductID = prod2.ProductID;
Test Numaraları 1, 2 ve 3 (Önbellekleme)
;WITH cte AS
(
SELECT prod1.ProductID
FROM Production.Product prod1 WITH (INDEX(AK_Product_Name))
WHERE prod1.Name LIKE N'[M-R]%'
AND EXISTS (
SELECT *
FROM Production.TransactionHistory th
WHERE th.ProductID = prod1.ProductID
)
)
SELECT prod2.ProductID, prod2.Name, prod2.DaysToManufacture
FROM Production.Product prod2
INNER JOIN cte
ON cte.ProductID = prod2.ProductID;
İşlem Bilgisi
Test Numaraları 1 ve 2 (Sabitler)
SELECT TOP (5) th.TransactionID, th.TransactionDate
FROM Production.TransactionHistory th
WHERE th.ProductID = 977
ORDER BY th.TransactionDate DESC;
Test Numaraları 1 ve 2 (Parametreli)
SELECT TOP (5) th.TransactionID, th.TransactionDate
FROM Production.TransactionHistory th
WHERE th.ProductID = @ProductID
ORDER BY th.TransactionDate DESC
;
Test Numaraları 1 ve 2 (Parametreli + OPTIMIZE UNKNOWN)
SELECT TOP (5) th.TransactionID, th.TransactionDate
FROM Production.TransactionHistory th
WHERE th.ProductID = @ProductID
ORDER BY th.TransactionDate DESC
OPTION (OPTIMIZE FOR (@ProductID UNKNOWN));
Test Numarası 2 (Her ikisi de Parametrelenmiş)
SELECT TOP (@RowsToReturn) th.TransactionID, th.TransactionDate
FROM Production.TransactionHistory th
WHERE th.ProductID = @ProductID
ORDER BY th.TransactionDate DESC
;
Test Numarası 2 (Her ikisi de Parametrelenmiş + OPTIMIZE UNKNOWN)
SELECT TOP (@RowsToReturn) th.TransactionID, th.TransactionDate
FROM Production.TransactionHistory th
WHERE th.ProductID = @ProductID
ORDER BY th.TransactionDate DESC
OPTION (OPTIMIZE FOR (@ProductID UNKNOWN));
Test Numarası 3 (Sabitler)
SELECT TOP (1) th.TransactionID, th.TransactionDate
FROM Production.TransactionHistory th
WHERE th.ProductID = 977
ORDER BY th.TransactionDate DESC, th.TransactionID DESC;
Test Numarası 3 (Parametreli)
SELECT TOP (1) th.TransactionID, th.TransactionDate
FROM Production.TransactionHistory th
WHERE th.ProductID = @ProductID
ORDER BY th.TransactionDate DESC, th.TransactionID DESC
;
Test Numarası 3 (Parametreli + OPTIMIZE UNKNOWN)
SELECT TOP (1) th.TransactionID, th.TransactionDate
FROM Production.TransactionHistory th
WHERE th.ProductID = @ProductID
ORDER BY th.TransactionDate DESC, th.TransactionID DESC
OPTION (OPTIMIZE FOR (@ProductID UNKNOWN));
Kod
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
public class ObligatoryClassName
{
private class ProductInfo
{
public int ProductID;
public string Name;
public int DaysToManufacture;
public ProductInfo(int ProductID, string Name, int DaysToManufacture)
{
this.ProductID = ProductID;
this.Name = Name;
this.DaysToManufacture = DaysToManufacture;
return;
}
}
private static readonly List<ProductInfo> _GlobalProducts = new List<ProductInfo>();
private static void PopulateGlobalProducts(SqlBoolean PrintQuery)
{
if (_GlobalProducts.Count > 0)
{
if (PrintQuery.IsTrue)
{
SqlContext.Pipe.Send(String.Concat("I already haz ", _GlobalProducts.Count,
" entries :)"));
}
return;
}
SqlConnection _Connection = new SqlConnection("Context Connection = true;");
SqlCommand _Command = new SqlCommand();
_Command.CommandType = CommandType.Text;
_Command.Connection = _Connection;
_Command.CommandText = @"
;WITH cte AS
(
SELECT prod1.ProductID
FROM Production.Product prod1 WITH (INDEX(AK_Product_Name))
WHERE prod1.Name LIKE N'[M-R]%'
AND EXISTS (
SELECT *
FROM Production.TransactionHistory th
WHERE th.ProductID = prod1.ProductID
)
)
SELECT prod2.ProductID, prod2.Name, prod2.DaysToManufacture
FROM Production.Product prod2
INNER JOIN cte
ON cte.ProductID = prod2.ProductID;
";
SqlDataReader _Reader = null;
try
{
_Connection.Open();
_Reader = _Command.ExecuteReader();
while (_Reader.Read())
{
_GlobalProducts.Add(new ProductInfo(_Reader.GetInt32(0), _Reader.GetString(1),
_Reader.GetInt32(2)));
}
}
catch
{
throw;
}
finally
{
if (_Reader != null && !_Reader.IsClosed)
{
_Reader.Close();
}
if (_Connection != null && _Connection.State != ConnectionState.Closed)
{
_Connection.Close();
}
if (PrintQuery.IsTrue)
{
SqlContext.Pipe.Send(_Command.CommandText);
}
}
return;
}
[Microsoft.SqlServer.Server.SqlProcedure]
public static void GetTopRowsPerGroup(SqlByte TestNumber,
SqlByte ParameterizeProductID, SqlBoolean OptimizeForUnknown,
SqlBoolean UseSequentialAccess, SqlBoolean CacheProducts, SqlBoolean PrintQueries)
{
SqlConnection _Connection = new SqlConnection("Context Connection = true;");
SqlCommand _Command = new SqlCommand();
_Command.CommandType = CommandType.Text;
_Command.Connection = _Connection;
List<ProductInfo> _Products = null;
SqlDataReader _Reader = null;
int _RowsToGet = 5; // default value is for Test Number 1
string _OrderByTransactionID = "";
string _OptimizeForUnknown = "";
CommandBehavior _CmdBehavior = CommandBehavior.Default;
if (OptimizeForUnknown.IsTrue)
{
_OptimizeForUnknown = "OPTION (OPTIMIZE FOR (@ProductID UNKNOWN))";
}
if (UseSequentialAccess.IsTrue)
{
_CmdBehavior = CommandBehavior.SequentialAccess;
}
if (CacheProducts.IsTrue)
{
PopulateGlobalProducts(PrintQueries);
}
else
{
_Products = new List<ProductInfo>();
}
if (TestNumber.Value == 2)
{
_Command.CommandText = @"
;WITH cte AS
(
SELECT prod1.ProductID
FROM Production.Product prod1 WITH (INDEX(AK_Product_Name))
WHERE prod1.Name LIKE N'[M-R]%'
)
SELECT prod2.ProductID, prod2.Name, prod2.DaysToManufacture
FROM Production.Product prod2
INNER JOIN cte
ON cte.ProductID = prod2.ProductID;
";
}
else
{
_Command.CommandText = @"
SELECT prod1.ProductID, prod1.Name, 1 AS [DaysToManufacture]
FROM Production.Product prod1
WHERE prod1.Name LIKE N'[M-R]%';
";
if (TestNumber.Value == 3)
{
_RowsToGet = 1;
_OrderByTransactionID = ", th.TransactionID DESC";
}
}
try
{
_Connection.Open();
// Populate Product list for this run if not using the Product Cache
if (!CacheProducts.IsTrue)
{
_Reader = _Command.ExecuteReader(_CmdBehavior);
while (_Reader.Read())
{
_Products.Add(new ProductInfo(_Reader.GetInt32(0), _Reader.GetString(1),
_Reader.GetInt32(2)));
}
_Reader.Close();
if (PrintQueries.IsTrue)
{
SqlContext.Pipe.Send(_Command.CommandText);
}
}
else
{
_Products = _GlobalProducts;
}
SqlDataRecord _ResultRow = new SqlDataRecord(
new SqlMetaData[]{
new SqlMetaData("ProductID", SqlDbType.Int),
new SqlMetaData("Name", SqlDbType.NVarChar, 50),
new SqlMetaData("TransactionID", SqlDbType.Int),
new SqlMetaData("TransactionDate", SqlDbType.DateTime)
});
SqlParameter _ProductID = new SqlParameter("@ProductID", SqlDbType.Int);
_Command.Parameters.Add(_ProductID);
SqlParameter _RowsToReturn = new SqlParameter("@RowsToReturn", SqlDbType.Int);
_Command.Parameters.Add(_RowsToReturn);
SqlContext.Pipe.SendResultsStart(_ResultRow);
for (int _Row = 0; _Row < _Products.Count; _Row++)
{
// Tests 1 and 3 use previously set static values for _RowsToGet
if (TestNumber.Value == 2)
{
if (_Products[_Row].DaysToManufacture == 0)
{
continue; // no use in issuing SELECT TOP (0) query
}
_RowsToGet = (5 * _Products[_Row].DaysToManufacture);
}
_ResultRow.SetInt32(0, _Products[_Row].ProductID);
_ResultRow.SetString(1, _Products[_Row].Name);
switch (ParameterizeProductID.Value)
{
case 0x01:
_Command.CommandText = String.Format(@"
SELECT TOP ({0}) th.TransactionID, th.TransactionDate
FROM Production.TransactionHistory th
WHERE th.ProductID = @ProductID
ORDER BY th.TransactionDate DESC{2}
{1};
", _RowsToGet, _OptimizeForUnknown, _OrderByTransactionID);
_ProductID.Value = _Products[_Row].ProductID;
break;
case 0x02:
_Command.CommandText = String.Format(@"
SELECT TOP (@RowsToReturn) th.TransactionID, th.TransactionDate
FROM Production.TransactionHistory th
WHERE th.ProductID = @ProductID
ORDER BY th.TransactionDate DESC
{0};
", _OptimizeForUnknown);
_ProductID.Value = _Products[_Row].ProductID;
_RowsToReturn.Value = _RowsToGet;
break;
default:
_Command.CommandText = String.Format(@"
SELECT TOP ({0}) th.TransactionID, th.TransactionDate
FROM Production.TransactionHistory th
WHERE th.ProductID = {1}
ORDER BY th.TransactionDate DESC{2};
", _RowsToGet, _Products[_Row].ProductID, _OrderByTransactionID);
break;
}
_Reader = _Command.ExecuteReader(_CmdBehavior);
while (_Reader.Read())
{
_ResultRow.SetInt32(2, _Reader.GetInt32(0));
_ResultRow.SetDateTime(3, _Reader.GetDateTime(1));
SqlContext.Pipe.SendResultsRow(_ResultRow);
}
_Reader.Close();
}
}
catch
{
throw;
}
finally
{
if (SqlContext.Pipe.IsSendingResults)
{
SqlContext.Pipe.SendResultsEnd();
}
if (_Reader != null && !_Reader.IsClosed)
{
_Reader.Close();
}
if (_Connection != null && _Connection.State != ConnectionState.Closed)
{
_Connection.Close();
}
if (PrintQueries.IsTrue)
{
SqlContext.Pipe.Send(_Command.CommandText);
}
}
}
}
Test Sorguları
Testleri buraya göndermek için yeterli alan yok, bu yüzden başka bir yer bulacağım.
Sonuç
Bazı senaryolar için, SQLCLR, T-SQL'de yapılamayan sorguların belirli yönlerini değiştirmek için kullanılabilir. Ve geçici tablolar yerine önbellekleme için bellek kullanma yeteneği vardır, ancak bellek otomatik olarak sisteme geri bırakılmadığından dikkatli ve dikkatli yapılmalıdır. Bu yöntem aynı zamanda geçici sorgulamalara yardımcı olacak bir şey değildir, ancak yürütülen sorguların daha fazla yönünü uyarlamak için parametreler ekleyerek burada gösterdiğimden daha esnek hale getirmek mümkündür.
GÜNCELLEME
Ek Test
Üzerine destekleyici bir dizin içeren orijinal testlerim TransactionHistory
aşağıdaki tanımı kullandı:
ProductID ASC, TransactionDate DESC
TransactionId DESC
Sonunda dahil olmaktan vazgeçmeye karar verdim, Test Numarası 3’ün (en yeni TransactionId
olanı bağladığını belirten - ki, “en son” un açıkça belirtilmediğinden beri varsayıldığını varsayıyordu, ancak herkes öyle görünüyordu) Bu varsayım üzerinde hemfikir olmak için), muhtemelen bir fark yaratmak için yeterli bağ olmayacaktı.
Ancak, daha sonra Aaron TransactionId DESC
, CROSS APPLY
yöntemin her üç testte de kazanan olduğunu içeren ve içeren bir destekleyici endeksiyle yeniden test etti. Bu, CTE yönteminin Test Numarası 3 için en iyisi olduğunu gösteren testlerimden farklıydı (önbellekleme kullanılmadığında, Aaron'un testini yansıtan). Test edilmesi gereken ek bir varyasyon olduğu açıktı.
Geçerli destek dizinini kaldırdım, yenisini oluşturdum TransactionId
ve plan önbelleğini temizledim (emin olmak için):
DROP INDEX [IX_TransactionHistoryX] ON Production.TransactionHistory;
CREATE UNIQUE INDEX [UIX_TransactionHistoryX]
ON Production.TransactionHistory (ProductID ASC, TransactionDate DESC, TransactionID DESC)
WITH (FILLFACTOR = 100);
DBCC FREEPROCCACHE WITH NO_INFOMSGS;
Test Numarası 1'i yeniden yaptım ve sonuçlar beklendiği gibi aynıydı. Daha sonra Test Numarası 3'ü tekrar değiştirdim ve sonuçlar gerçekten değişti:
Yukarıdaki sonuçlar standart, önbelleğe almayan test içindir. Bu sefer, sadece CROSS APPLY
CTE'yi yenmekle kalmıyor (aynı Aaron'un testinde belirtildiği gibi), ancak SQLCLR proc'u 30 Reads (woo hoo) götürdü.
Yukarıdaki sonuçlar, önbellekleme etkinleştirilmiş test içindir. Bu sefer CTE'nin performansı düşmese de, yine CROSS APPLY
de atıyor. Ancak, şimdi SQLCLR proc 23 okur (woo hoo, yine).
Uzaklaş
Kullanmak için çeşitli seçenekler var. Her birinin güçlü yanları olduğu için birkaçını denemek en iyisidir. Burada yapılan testler, tüm testlerde (destekleyici bir indeksle) en iyi ve en kötü performans gösterenler arasında hem Okuma hem de Süre'de oldukça küçük bir farklılık göstermektedir; Okurlardaki değişim yaklaşık 350 ve Süre 55 ms'dir. SQLCLR proc, 1 test haricinde (Okurlar açısından) hepsinde kazanmış olsa da, sadece birkaç Okur kaydetmek genellikle SQLCLR yoluna gitmenin bakım maliyetine değmez. Ancak AdventureWorks2012'de, Product
tablo sadece 504 satıra ve TransactionHistory
yalnızca 113.443 satıra sahiptir. Bu yöntemler arasındaki performans farkı, satır sayıları arttıkça muhtemelen daha belirgin hale gelir.
Bu soru, belirli bir satır dizisini elde etmeye özgüyken, performanstaki tek en büyük faktörün, belirli bir SQL değil, dizin oluşturduğu göz ardı edilmemelidir. Hangi yöntemin gerçekten en iyi olduğuna karar vermeden önce iyi bir endeksin yer alması gerekir.
Burada bulunan en önemli ders CROSS APPLY - CTE - SQLCLR hakkında değil: TESTING hakkında. Farz etme. Birkaç kişiden fikir alın ve mümkün olduğu kadar çok senaryoyu test edin.