Her SET STATISTICS IO ON
ikisi için de çıktı benzer görünüyor
SET STATISTICS IO ON;
PRINT 'V2'
EXEC dbo.V2 10
PRINT 'T2'
EXEC dbo.T2 10
verir
V2
Table '#58B62A60'. Scan count 0, logical reads 20
Table 'NUM'. Scan count 1, logical reads 3
Table '#58B62A60'. Scan count 10, logical reads 20
Table 'NUM'. Scan count 1, logical reads 3
T2
Table '#T__ ... __00000000E2FE'. Scan count 0, logical reads 20
Table 'NUM'. Scan count 1, logical reads 3
Table '#T__ ... __00000000E2FE'. Scan count 0, logical reads 20
Table 'NUM'. Scan count 1, logical reads 3
Ve Aaron yorumlarda da belirtildiği gibi, tablo değişken sürümü için plan aslında daha az etkilidir, çünkü her ikisi de dbo.NUM
, #temp
tablo sürümünde bir indeks arama tarafından yönlendirilen bir iç içe döngü planına sahipken, tablo değişkeninde [#T].n = [dbo].[NUM].[n]
kalan [#T].[n]<=[@total]
değişken ile endekse bir arama yapar. sürüm @V.n <= [@total]
, artık yüklem ile bir dizin araması gerçekleştirir @V.[n]=[dbo].[NUM].[n]
ve bu nedenle daha fazla satır işler (bu plan daha fazla satır için bu kadar düşük performans gösteriyor)
Belirli spidlerin bekleme türlerine bakmak için Genişletilmiş Olayları kullanmak , bu sonuçları 10.000 çalıştırma için verir.EXEC dbo.T2 10
+---------------------+------------+----------------+----------------+----------------+
| | | Total | Total Resource | Total Signal |
| Wait Type | Wait Count | Wait Time (ms) | Wait Time (ms) | Wait Time (ms) |
+---------------------+------------+----------------+----------------+----------------+
| SOS_SCHEDULER_YIELD | 16 | 19 | 19 | 0 |
| PAGELATCH_SH | 39998 | 14 | 0 | 14 |
| PAGELATCH_EX | 1 | 0 | 0 | 0 |
+---------------------+------------+----------------+----------------+----------------+
ve bu sonuçlar 10.000 EXEC dbo.V2 10
+---------------------+------------+----------------+----------------+----------------+
| | | Total | Total Resource | Total Signal |
| Wait Type | Wait Count | Wait Time (ms) | Wait Time (ms) | Wait Time (ms) |
+---------------------+------------+----------------+----------------+----------------+
| PAGELATCH_EX | 2 | 0 | 0 | 0 |
| PAGELATCH_SH | 1 | 0 | 0 | 0 |
| SOS_SCHEDULER_YIELD | 676 | 0 | 0 | 0 |
+---------------------+------------+----------------+----------------+----------------+
Bu nedenle, masadaki PAGELATCH_SH
bekleme sayısının çok daha fazla olduğu açıktır #temp
. Genişletti olayları izlemesine bekleme kaynağını eklemenin herhangi bir yolunun farkında değilim.
WHILE 1=1
EXEC dbo.T2 10
Başka bir bağlantıda oylama yaparken sys.dm_os_waiting_tasks
CREATE TABLE #T(resource_description NVARCHAR(2048))
WHILE 1=1
INSERT INTO #T
SELECT resource_description
FROM sys.dm_os_waiting_tasks
WHERE session_id=<spid_of_other_session> and wait_type='PAGELATCH_SH'
Bu koşuyu yaklaşık 15 saniye çalıştırdıktan sonra, aşağıdaki sonuçları topladı.
+-------+----------------------+
| Count | resource_description |
+-------+----------------------+
| 1098 | 2:1:150 |
| 1689 | 2:1:146 |
+-------+----------------------+
Bu sayfaların her ikisi üzerinde (farklı) olmayan kümelenmiş dizinleri aittir kilitli olan tempdb.sys.sysschobjs
taban adlı tabloya 'nc1'
ve 'nc2'
.
Çalışmalar tempdb.sys.fn_dblog
sırasında sorgulama , her saklı prosedürün ilk uygulaması tarafından eklenen log kayıtlarının sayısının bir miktar değişken olduğunu ancak sonraki işlemler için her bir yinelemenin eklediği sayının çok tutarlı ve öngörülebilir olduğunu gösterir. Prosedür planları önbelleğe alındıktan sonra, günlük girişlerinin sayısı #temp
sürüm için gerekenlerin yaklaşık yarısı kadardır .
+-----------------+----------------+------------+
| | Table Variable | Temp Table |
+-----------------+----------------+------------+
| First Run | 126 | 72 or 136 |
| Subsequent Runs | 17 | 32 |
+-----------------+----------------+------------+
#temp
SP işlem tablosu girişleri için işlem günlüğü girişlerini daha ayrıntılı incelemek, her bir saklı yordamın çağrılmasının ardından üç işlem ve tablo değişkeni yalnızca iki işlem oluşturur.
+---------------------------------+----+---------------------------------+----+
| #Temp Table | @Table Variable |
+---------------------------------+----+---------------------------------+----+
| CREATE TABLE | 9 | | |
| INSERT | 12 | TVQuery | 12 |
| FCheckAndCleanupCachedTempTable | 11 | FCheckAndCleanupCachedTempTable | 5 |
+---------------------------------+----+---------------------------------+----+
INSERT
/ TVQUERY
İşlemler ismi hariç özdeştir. Bu, geçici tabloya veya tablo değişkenine eklenmiş 10 satırın her birinin ve LOP_BEGIN_XACT
/ LOP_COMMIT_XACT
girdilerinin günlük kayıtlarını içerir .
CREATE TABLE
İşlem yalnızca görünen #Temp
sürümü ve aşağıdaki gibi görünüyor.
+-----------------+-------------------+---------------------+
| Operation | Context | AllocUnitName |
+-----------------+-------------------+---------------------+
| LOP_BEGIN_XACT | LCX_NULL | |
| LOP_SHRINK_NOOP | LCX_NULL | |
| LOP_MODIFY_ROW | LCX_CLUSTERED | sys.sysschobjs.clst |
| LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysschobjs.nc1 |
| LOP_INSERT_ROWS | LCX_INDEX_LEAF | sys.sysschobjs.nc1 |
| LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysschobjs.nc2 |
| LOP_INSERT_ROWS | LCX_INDEX_LEAF | sys.sysschobjs.nc2 |
| LOP_MODIFY_ROW | LCX_CLUSTERED | sys.sysschobjs.clst |
| LOP_COMMIT_XACT | LCX_NULL | |
+-----------------+-------------------+---------------------+
FCheckAndCleanupCachedTempTable
İşlemin hem görünür ancak içinde 6 ek girişler var #temp
sürümü. Bunlar atıfta bulunan 6 satırdır sys.sysschobjs
ve yukarıdakilerle tamamen aynı desene sahiptir.
+-----------------+-------------------+----------------------------------------------+
| Operation | Context | AllocUnitName |
+-----------------+-------------------+----------------------------------------------+
| LOP_BEGIN_XACT | LCX_NULL | |
| LOP_DELETE_ROWS | LCX_NONSYS_SPLIT | dbo.#7240F239.PK__#T________3BD0199374293AAB |
| LOP_HOBT_DELTA | LCX_NULL | |
| LOP_HOBT_DELTA | LCX_NULL | |
| LOP_MODIFY_ROW | LCX_CLUSTERED | sys.sysschobjs.clst |
| LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysschobjs.nc1 |
| LOP_INSERT_ROWS | LCX_INDEX_LEAF | sys.sysschobjs.nc1 |
| LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysschobjs.nc2 |
| LOP_INSERT_ROWS | LCX_INDEX_LEAF | sys.sysschobjs.nc2 |
| LOP_MODIFY_ROW | LCX_CLUSTERED | sys.sysschobjs.clst |
| LOP_COMMIT_XACT | LCX_NULL | |
+-----------------+-------------------+----------------------------------------------+
Her iki işlemde de bu 6 satıra bakıldığında, aynı işlemlere karşılık gelirler. İlki LOP_MODIFY_ROW, LCX_CLUSTERED
, içindeki modify_date
sütuna yapılan bir güncellemedir sys.objects
. Kalan beş satırın tümü nesne yeniden adlandırma ile ilgilidir. Çünkü name
etkilenen her iki NCI'nin ( nc1
ve nc2
) ana sütunu budur, bu olanlar için bir silme / ekleme olarak gerçekleştirilir, daha sonra kümelenmiş dizine geri döner ve bunu da günceller.
#temp
Tablo sürümü için, saklı yordamın FCheckAndCleanupCachedTempTable
işlem tarafından gerçekleştirilen temizlemenin bir kısmı sona erdiğinde , geçici çizelgenin benzeri #T__________________________________________________________________________________________________________________00000000E316
bir şeyden farklı bir iç isme yeniden adlandırıldığı #2F4A0079
ve CREATE TABLE
işlem girildiği zaman işlemin yeniden adlandırıldığı anlaşılmaktadır. Bu çevirme kayma adı, diğerinde dbo.T2
iken bir döngüde yürütülen bir bağlantıda görülebilir.
WHILE 1=1
SELECT name, object_id, create_date, modify_date
FROM tempdb.sys.objects
WHERE name LIKE '#%'
Örnek Sonuçlar
Dolayısıyla, Alex'in ima ettiği gibi gözlenen performans farklılığı için olası bir açıklama, sistem tablolarını bunun içinde tutan bu ek çalışma olduğudur tempdb
.
Her iki yordamı bir döngüde çalıştırdığınızda, Visual Studio Code profilcisi aşağıdakileri ortaya çıkarır
+-------------------------------+--------------------+-------+-----------+
| Function | Explanation | Temp | Table Var |
+-------------------------------+--------------------+-------+-----------+
| CXStmtDML::XretExecute | Insert ... Select | 16.93 | 37.31 |
| CXStmtQuery::ErsqExecuteQuery | Select Max | 8.77 | 23.19 |
+-------------------------------+--------------------+-------+-----------+
| Total | | 25.7 | 60.5 |
+-------------------------------+--------------------+-------+-----------+
Tablo değişkeni sürümü, insert ifadesini ve sonraki seçimi yapan sürenin yaklaşık% 60'ını harcar, geçici tablo ise yarısından azdır. Bu, OP'de gösterilen zamanlamalarla aynıdır ve yukarıdaki sonuçla, performanstaki farkın, sorgu çalışmasının kendisinde geçirilen zamandan ötürü değil, yardımcı çalışma yapmak için harcanan zamana göre olduğu sonucuna varılmıştır.
Geçici tablo versiyonunda "eksik"% 75'e katkıda bulunan en önemli fonksiyonlar
+------------------------------------+-------------------+
| Function | Inclusive Samples |
+------------------------------------+-------------------+
| CXStmtCreateTableDDL::XretExecute | 26.26% |
| CXStmtDDL::FinishNormalImp | 4.17% |
| TmpObject::Release | 27.77% |
+------------------------------------+-------------------+
| Total | 58.20% |
+------------------------------------+-------------------+
Hem yarat hem de bırak işlevlerinin altında, işlev CMEDProxyObject::SetName
, kapsayıcı bir örnek değeriyle gösterilir 19.6%
. Bundan geçici tablodaki zamanın% 39.2'sinin daha önce tarif edilen yeniden isimlendirmeyle alındığı sonucuna vardım.
Tablo değişken versiyonundaki ve% 40’a katkıda bulunanların en büyüğü
+-----------------------------------+-------------------+
| Function | Inclusive Samples |
+-----------------------------------+-------------------+
| CTableCreate::LCreate | 7.41% |
| TmpObject::Release | 12.87% |
+-----------------------------------+-------------------+
| Total | 20.28% |
+-----------------------------------+-------------------+
Geçici Masa Profili
Tablo Değişken Profil
#temp
masaya ancak bir kez daha oluşturulmasına ve ardından 9.999 kez daha doldurulmasına rağmen bir kez oluşturulduğunu gösterir .