Bu uygulama planını açıklayabilir misiniz?


20

Bu şeyle karşılaştığımda başka bir şey araştırıyordum. İçinde bazı verilerle test tabloları oluşturuyordum ve sorgu yazmanın farklı yollarının yürütme planını nasıl etkilediğini öğrenmek için farklı sorgular çalıştırıyordum. İşte rastgele test verileri oluşturmak için kullanılan komut dosyası:

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID('t') AND type in (N'U'))
DROP TABLE t
GO

CREATE TABLE t 
(
 c1 int IDENTITY(1,1) NOT NULL 
,c2 int NULL
) 
GO

insert into t
select top 1000000 a from
(select t1.number*2048 + t2.number a, newid() b
from [master]..spt_values t1 
cross join  [master]..spt_values t2
where t1.[type] = 'P' and t2.[type] = 'P') a
order by b
GO

update t set c2 = null
where c2 < 2048 * 2048 / 10
GO


CREATE CLUSTERED INDEX pk ON [t] (c1)
GO

CREATE NONCLUSTERED INDEX i ON t (c2)
GO

Şimdi, bu veriler göz önüne alındığında, aşağıdaki sorguyu çağırdı:

select * 
from t 
where 
      c2 < 1048576 
   or c2 is null
;

Benim için büyük sürpriz, bu sorgu için oluşturulan yürütme planı için oldu bu . (Harici bağlantı için üzgünüm, buraya sığmayacak kadar büyük).

Birisi bana tüm bu " Sürekli Taramalar " ve " Hesaplamalı Taramalar " ile ilgili neler olduğunu açıklayabilir mi? Ne oluyor?

Plan

  |--Nested Loops(Inner Join, OUTER REFERENCES:([Expr1010], [Expr1011], [Expr1012]))
       |--Merge Interval
       |    |--Sort(TOP 2, ORDER BY:([Expr1013] DESC, [Expr1014] ASC, [Expr1010] ASC, [Expr1015] DESC))
       |         |--Compute Scalar(DEFINE:([Expr1013]=((4)&[Expr1012]) = (4) AND NULL = [Expr1010], [Expr1014]=(4)&[Expr1012], [Expr1015]=(16)&[Expr1012]))
       |              |--Concatenation
       |                   |--Compute Scalar(DEFINE:([Expr1005]=NULL, [Expr1006]=NULL, [Expr1004]=(60)))
       |                   |    |--Constant Scan
       |                   |--Compute Scalar(DEFINE:([Expr1008]=NULL, [Expr1009]=(1048576), [Expr1007]=(10)))
       |                        |--Constant Scan
       |--Index Seek(OBJECT:([t].[i]), SEEK:([t].[c2] > [Expr1010] AND [t].[c2] < [Expr1011]) ORDERED FORWARD)

Yanıtlar:


29

Sabit taramaların her biri sütun içermeyen tek bir bellek içi satır oluşturur. En üst hesaplama skaleri 3 sütunlu tek bir satır çıkarır

Expr1005    Expr1006    Expr1004
----------- ----------- -----------
NULL        NULL        60

Alt hesaplama skaleri 3 sütunlu tek bir satır çıkarır

Expr1008    Expr1009    Expr1007
----------- ----------- -----------
NULL        1048576        10

Birleştirme operatörü bu 2 satırı bir araya getirir ve 3 sütunu çıkarır, ancak şimdi yeniden adlandırılmıştır

Expr1010    Expr1011    Expr1012
----------- ----------- -----------
NULL        NULL        60
NULL        1048576     10

Expr1012Sütun bayrakların kümesidir belli tanımlamak için dahili olarak kullanılan Depolama Engine özelliklerini aramaya .

Bir sonraki hesaplama skaleri çıkışlar 2 sıra

Expr1010    Expr1011    Expr1012    Expr1013    Expr1014    Expr1015
----------- ----------- ----------- ----------- ----------- -----------
NULL        NULL        60          True        4           16            
NULL        1048576     10          False       0           0      

Son üç sütun aşağıdaki gibi tanımlanır ve Birleştirme Aralığı İşleci'ne sunulmadan önce yalnızca sıralama amacıyla kullanılır

[Expr1013] = Scalar Operator(((4)&[Expr1012]) = (4) AND NULL = [Expr1010]), 
[Expr1014] = Scalar Operator((4)&[Expr1012]), 
[Expr1015] = Scalar Operator((16)&[Expr1012])

Expr1014ve Expr1015bayrakta belirli bitlerin açık olup olmadığını test edin. Expr1013Her iki bit true bir boolean sütun dönmek göründüğü 4açık ve Expr1010olduğunu NULL.

Sorgudaki diğer karşılaştırma işleçlerini denemeden bu sonuçları alıyorum

+----------+----------+----------+-------------+----+----+---+---+---+---+
| Operator | Expr1010 | Expr1011 | Flags (Dec) |       Flags (Bin)       |
|          |          |          |             | 32 | 16 | 8 | 4 | 2 | 1 |
+----------+----------+----------+-------------+----+----+---+---+---+---+
| >        | 1048576  | NULL     |           6 |  0 |  0 | 0 | 1 | 1 | 0 |
| >=       | 1048576  | NULL     |          22 |  0 |  1 | 0 | 1 | 1 | 0 |
| <=       | NULL     | 1048576  |          42 |  1 |  0 | 1 | 0 | 1 | 0 |
| <        | NULL     | 1048576  |          10 |  0 |  0 | 1 | 0 | 1 | 0 |
| =        | 1048576  | 1048576  |          62 |  1 |  1 | 1 | 1 | 1 | 0 |
| IS NULL  | NULL     | NULL     |          60 |  1 |  1 | 1 | 1 | 0 | 0 |
+----------+----------+----------+-------------+----+----+---+---+---+---+

Bundan, Bit 4'ün "sınırın başlaması var" (sınırsız olmanın aksine) anlamına geldiğini ve Bit 16, aralığın başlangıcı dahil olduğu anlamına gelir.

Bu 6 sütun sonuç kümesi, SORTsıralama ölçütü olan operatörden yayılır Expr1013 DESC, Expr1014 ASC, Expr1010 ASC, Expr1015 DESC. Varsayarsak Trueile temsil edilir 1ve Falsetarafından 0daha önce temsil resultset'de bu sırayla zaten.

Önceki varsayımlarıma dayanarak, bu türün net etkisi, aralıkları birleştirme aralığına aşağıdaki sırayla sunmaktır.

 ORDER BY 
          HasStartOfRangeAndItIsNullFirst,
          HasUnboundedStartOfRangeFirst,
          StartOfRange,
          StartOfRangeIsInclusiveFirst

Birleştirme aralığı operatörü 2 satır çıkarır

Expr1010    Expr1011    Expr1012
----------- ----------- -----------
NULL        NULL        60
NULL        1048576     10

Yayılan her satır için bir aralık araması yapılır

Seek Keys[1]: Start:[dbo].[t].c2 > Scalar Operator([Expr1010]), 
               End: [dbo].[t].c2 < Scalar Operator([Expr1011])

Yani iki arama yapılmış gibi görünecektir. Biri görünüşte > NULL AND < NULLbiri > NULL AND < 1048576. Ancak, geçen bayraklar bunu sırasıyla IS NULLve şeklinde < 1048576değiştirir. Umarım @sqlkiwi bunu açıklığa kavuşturabilir ve yanlışlıkları düzeltebilir!

Sorguyu biraz değiştirirseniz

select *
from t 
where 
      c2 > 1048576 
   or c2 = 0
;

Daha sonra plan, çoklu arama tahminleri içeren bir dizin aramasıyla çok daha basit görünüyor.

Plan gösteriyor Seek Keys

Start: c2 >= 0, End: c2 <= 0, 
Start: c2 > 1048576

Bu daha basit planın OP'deki durum için neden kullanılamadığına ilişkin açıklama, SQLKiwi tarafından önceki bağlantılı blog gönderisine yapılan yorumlarda verilmiştir .

Birden yüklemler karşılaştırma yüklemi (yani. Değişik türde olamaz sahip bir dizin arama Isve EqOP durumunda). Bu ürünün sadece bir akım sınırlama (ve tahminen son sorguda eşitlik testi nedeni budur c2 = 0kullanılarak uygulanır >=ve <=yerine sadece basit eşitlik daha sorgu için olsun aramaya c2 = 0 OR c2 = 1048576.


Paul'ün makalesinde [Expr1012] için bayraklardaki farkı açıklayan hiçbir şeyi göremiyorum. 60/10'un burada ne ifade ettiğini söyleyebilir misiniz?
Mark Storey-Smith

@ MarkStorey-Smith - 62eşitlik karşılaştırması için olduğunu söylüyor . Sanırım 60o yerine demek olmalı > AND < sen aslında içerisinde olsun planında gösterildiği gibi >= AND <=bir açık olmadıkça IS NULLbayrak belki (?) Ya da belki biraz 2başka ilgisiz bir şey gösterir ve 60hala yapmam olduğu gibi eşitlik olduğunu set ansi_nulls offve bunu değiştirmek c2 = nullde hala kalır60
Martin Smith

2
@MartinSmith 60 gerçekten NULL ile karşılaştırma içindir. Aralık sınırı ifadeleri, her iki uçta da 'sınırsız'ı temsil etmek için NULL kullanır. Arama her zaman münhasırdır. Blog yorumu için teşekkürler, cevap olarak bir cevap veya daha uzun bir yorum yayınlayacağım (şu anda adalet yapmak için çok geç).
Paul White, GoFundMonica'yı anlatıyor

@SQLKiwi - Teşekkürler. Mantıklı. Umarım o zamandan önce bazı eksik parçaları bulmuş olurum.
Martin Smith

Çok teşekkür ederim, hala bunu özlüyorum, ama şeyleri güzelce açıklıyor gibi görünüyor, kalan ana soru blogunda @SQLKiwi sorduğunuz sorudur. Takip sorularım olmadığından emin olmak için cevabınız üzerine birkaç gün daha meditasyon yapacağım ve cevabınızı kabul edeceğim. Tekrar teşekkür ederim, çok yardımcı oldu.
Andrew Savinykh

13

Sabit taramalar, SQL Server'ın yürütme planına daha sonra bir şey yerleştireceği bir kova oluşturmasının bir yoludur. Burada daha ayrıntılı bir açıklama yaptım . Sürekli taramanın ne için olduğunu anlamak için plana daha fazla bakmanız gerekir. Bu durumda, sürekli tarama tarafından oluşturulan alanı doldurmak için kullanılan Hesaplama Skaler işleçleridir.

Hesaplama Skaler operatörleri NULL ve 1045876 değeri ile yüklenmektedir, bu nedenle verileri filtrelemek için Loop Join ile açıkça kullanılacaktır.

Gerçekten güzel olan kısım, bu planın önemsiz olmasıdır. Bu, minimal bir optimizasyon sürecinden geçtiğini gösterir. Tüm işlemler Birleştirme Aralığı'na ilerler. Bu, bir dizin araması için minimal bir karşılaştırma işleçleri kümesi oluşturmak için kullanılır ( ayrıntılar burada ).

Tüm fikir, üst üste binen değerlerden kurtulmak, böylece verileri minimum geçişlerle çekebilir. Hala bir döngü işlemi kullanıyor olsa da, döngünün tam olarak bir kez yürütüldüğünü, yani etkili bir tarama olduğunu unutmayın.

ADDENDUM: Son cümle kapalı. İki arayış vardı. Planı yanlış okudum. Kavramların geri kalanı aynıdır ve amaç, minimum geçişler aynıdır.

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.