Her SET STATISTICS IO ONikisi 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, #temptablo 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_SHbekleme 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.sysschobjstaban adlı tabloya 'nc1've 'nc2'.
Çalışmalar tempdb.sys.fn_dblogsı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ı #tempsü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 |
+-----------------+----------------+------------+
#tempSP 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_XACTgirdilerinin günlük kayıtlarını içerir .
CREATE TABLEİşlem yalnızca görünen #Tempsü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 #tempsürümü. Bunlar atıfta bulunan 6 satırdır sys.sysschobjsve 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_datesütuna yapılan bir güncellemedir sys.objects. Kalan beş satırın tümü nesne yeniden adlandırma ile ilgilidir. Çünkü nameetkilenen her iki NCI'nin ( nc1ve 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.
#tempTablo sürümü için, saklı yordamın FCheckAndCleanupCachedTempTableişlem tarafından gerçekleştirilen temizlemenin bir kısmı sona erdiğinde , geçici çizelgenin benzeri #T__________________________________________________________________________________________________________________00000000E316bir şeyden farklı bir iç isme yeniden adlandırıldığı #2F4A0079ve CREATE TABLEişlem girildiği zaman işlemin yeniden adlandırıldığı anlaşılmaktadır. Bu çevirme kayma adı, diğerinde dbo.T2iken 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

#tempmasaya 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 .