SQL Server basit bijection üzerinde dizin kullanamıyor


11

Bu başka bir sorgu optimizer bilmecesidir.

Belki sadece sorgu optimize edicileri aşırı tahmin ediyorum, ya da belki bir şey eksik - bu yüzden orada koyuyorum.

Basit bir masam var

CREATE TABLE [dbo].[MyEntities](
  [Id] [uniqueidentifier] NOT NULL,
  [Number] [int] NOT NULL,
  CONSTRAINT [PK_dbo.MyEntities] PRIMARY KEY CLUSTERED ([Id])
)

CREATE NONCLUSTERED INDEX [IX_Number] ON [dbo].[MyEntities] ([Number])

bir indeks ve orada bin sıra ile Number0, 1 ve 2 değerlerinde eşit olarak dağıtılır.

Şimdi bu sorgu:

SELECT * FROM
    (SELECT
        [Extent1].[Number] AS [Number],
        CASE
        WHEN (0 = [Extent1].[Number]) THEN 'one'
        WHEN (1 = [Extent1].[Number]) THEN 'two'
        WHEN (2 = [Extent1].[Number]) THEN 'three'
        ELSE '?'
        END AS [Name]
        FROM [dbo].[MyEntities] AS [Extent1]
        ) P
WHERE P.Number = 0;

IX_Numberbeklendiği gibi bir endeks arayışı yapar .

Eğer nerede cümlesi

WHERE P.Name = 'one';

ancak bir tarama haline gelir.

Vaka cümlesi açık bir şekilde bir iki yönlüdür, bu nedenle teoride birinci sorgu planını ikinci sorgudan çıkarmak için bir optimizasyon mümkün olmalıdır.

Aynı zamanda sadece akademik değil: Sorgu, enum değerlerini ilgili kolay adlarına çevirerek esinlenmiştir.

Sorgu optimize edicilerden (ve özellikle Sql Server'da bir) ne olacağını bilen birinden duymak istiyorum: Sadece çok fazla bekliyorum?

Daha önce bir sorgu biraz varyasyon bir optimizasyon aniden ortaya geleceğini durumlarda vardı soruyorum.

Sql Server 2016 Developer Edition kullanıyorum.

Yanıtlar:


18

Sadece çok mu fazlasını bekliyorum?

Evet. En azından ürünün mevcut sürümlerinde.

SQL Server CASEdeyimi ayırmayacak ve hesaplanan sütunun sonucunun 'one'o zaman [Extent1].[Number]olması gerektiğini bulmak için tersine mühendislik 0.

Sarsılmak için tahminlerinizi yazdığınızdan emin olmanız gerekir. Hangi hemen hemen her zaman formda olmasını içerir. basetable_column_name comparison_operator expression.

Küçük sapmalar bile kırılganlığı bozar.

WHERE P.Number + 0 = 0;

basitleştirmek, CASEifadeden daha basit olsa da, bir dizin araması kullanmaz .

Bir dize adı üzerinde arama yapmak ve numaraya bir arama almak istiyorsanız, ad ve sayıları içeren bir eşleme tablosuna ihtiyacınız olacak ve sorguda ona katılacaksınız, planın eşleme tablosunda bir arama ve ardından ilişkili bir arama olabilir. üzerinde [dbo].[MyEntities]dönen numarası ile ilk ararlar.


6

Numaralandırmanızı vaka bildirimi olarak yansıtmayın. Aşağıdaki gibi türetilmiş bir tablo olarak yansıtın:

SELECT * FROM
   (SELECT
      [Extent1].[Number] AS [Number],
      enum.Name
   FROM
      [dbo].[MyEntities] AS [Extent1]
      LEFT JOIN (VALUES
         (0, 'one'),
         (1, 'two'),
         (2, 'three')
      ) enum (Number, Name)
         ON Extent1.Number = enum.Number
   ) P
WHERE
   P.Name = 'one';

Daha iyi sonuçlar alacağınızdan şüpheleniyorum. ( ?Bu, büyük olasılıkla performans kazanımlarına engel olacağından, eksik olduğunda Adı dönüştürmedim . Ancak, WHEREyüklemi enumtabloya koymak için yan tümceyi yan tümceye taşıyabilir veya iki sütunu iç sorgu, biri yüklem için ve diğeri görüntüleme için; burada yüklem, NULLeşleşen numaralandırma değeri olmadığındadır.)

Yine de, [Extent1]orada nedeniyle , Entity Framework veya Linq-To-SQL gibi bir ORM kullandığınızı tahmin ediyorum . Böyle bir projeksiyonu yerel olarak nasıl gerçekleştireceğiniz konusunda size rehberlik edemem, ancak farklı bir teknik kullanabilirsiniz.

Bir projemde, enum değerlerini veritabanına birleştiren özel bir derleme sınıfıyla, kod enum değerlerini veritabanındaki gerçek tablolara yansıttım. (Enum değerlerinizi açıkça listelemeniz gereken kurallara uymanız, tablolarınızı gözden geçirmeden hiçbir zaman silmemeniz ve asla değiştiremezsiniz, ancak mevcut kurulumunuzda bunun en azından bir kısmını gözlemlemeniz gerekir) .

Şimdi, Identifierbirçok farklı beton alt sınıfı olan bir numaralandırma sınıfını kullanıyordum, ancak sade bir vanilya enum ile yapılamamasının bir nedeni yok. İşte bir örnek kullanım:

new EnumOrIdentifierProjector<CodeClassOrEnum, PrivateDbDtoObject>(
   _sqlConnector.Connection,
   "dbo.TableName",
   "PrimaryKeyId",
   "NameColumnName",
   dtoObject => dtoObject.PrimaryKeyId,
   dtoObject => dtoObject.NameField,
   EnumerableOfIdentifierOrTypeOfEnum
)
   .Populate();

Veritabanı değerlerini yazmak ve okumak için gerekli tüm bilgileri aktardığımı görebilirsiniz. (Geçerli istek tüm mevcut değerleri içermeyebilir bir durum vardı, bu yüzden veritabanından yanı sıra şu anda yüklü kümesi herhangi bir ek döndürmek gerekiyordu. Ben de veritabanı numaralarını atamasına izin, bir numaralandırma için muhtemelen olmaz bunu istiyorum.)

Fikir, başlangıçta sadece bir kez okunan / yazılan bir tablonuz olduğunda, tüm numaralandırma değerlerine güvenilir bir şekilde sahip olacak, diğer tablolar gibi ona katılıp performansın iyi olması gerektiğidir.

Umarım bu fikirler bir gelişme sağlamanız için yeterlidir.


Evet, EntityFramework kullanıyorum ve burada çözümün gerçekten optimal bir dünyada olması gerektiği yer var. Bu gerçekleşmeden önce öneriniz inandığım en iyi çözümlerden biri.
John

5

Soruyu genel olarak optimize edicilerle ilgilendiğiniz, ancak SQL Server için özel bir ilgiyle yorumladınız. Senaryonuzu db2 LUW V11.1 ile test ettim:

]$ db2 "create table myentities ( id int not null, number int not null )"
]$ db2 "create index ix_number on myentities (number)"
]$ db2 "insert into myentities (id, number) with t(n) as ( values 0 union all select n+1 from t where n<10000) select n, mod(n,3) from t"

DB2'deki iyileştirici ikinci sorguyu birincisine yeniden yazar:

Original Statement:
------------------
SELECT 
  * 
FROM 
  (SELECT 
     number,

   CASE 
   WHEN (0 = Number) 
   THEN 'one' 
   WHEN (1 = Number) 
   THEN 'two' 
   WHEN (2 = Number) 
   THEN 'three' 
   ELSE '?' END AS Name 
   FROM 
     MyEntities
  ) P 
WHERE 
  P.name = 'one'


Optimized Statement:
-------------------
SELECT 
  Q1.NUMBER AS "NUMBER",

CASE 
WHEN (0 = Q1.NUMBER) 
THEN 'one' 
WHEN (1 = Q1.NUMBER) 
THEN 'two' 
WHEN (2 = Q1.NUMBER) 
THEN 'three' 
ELSE '?' END AS "NAME" 
FROM 
  LELLE.MYENTITIES AS Q1 
WHERE 
  (0 = Q1.NUMBER)

Plan şöyle görünüyor:

Access Plan:
-----------
        Total Cost:             33.5483
        Query Degree:           1


      Rows 
     RETURN
     (   1)
      Cost 
       I/O 
       |
      3334 
     IXSCAN
     (   2)
     33.1861 
     4.66713 
       |
      10001 
 INDEX: LELLE   
    IX_NUMBER
       Q1

Diğer optimizatörler hakkında fazla bir şey bilmiyorum, ancak DB2 optimizatörünün rakipler arasında bile oldukça iyi olduğu hissine kapıldım.


Bu çok heyecan verici. "Optimize edilmiş ifadenin" nereden geldiğine ışık tutabilir misiniz? Db2 bunu size geri veriyor mu? - Ayrıca, planı okumakta zorlanıyorum. Ben does it "IXSCAN" almak değil , bu durumda ortalama indeks taraması?
John

1
DB2'ye sizin için bir açıklama açıklamasını söyleyebilirsiniz. Toplanan bilgiler bir tablo kümesinde saklanır ve görsel açıklama veya bu durumda db2exfmt yardımcı programını kullanabilirsiniz (veya kendi util'inizi oluşturabilirsiniz). Ek olarak, bir ifadeyi izleyebilir ve plandaki tahmini kardinaliteyi gerçek planla karşılaştırabilirsiniz. Bu planda gerçekten bir indexscan (IXSCAN) olduğunu ve bu operatörden tahmini çıktı 3334 satır olduğunu görebiliriz. SQL sunucusunda bu kötü mü? Startkey ve stopkey anahtarlarını bilir, böylece sadece DB2'deki ilgili satırları tarar.
Lennart

Bu nedenle tarama olarak adlandırdığı şey, Sql Server'ın eşdeğer plan açıklamaları aramayı içerir ve dürüst olmak gerekirse, bazen bir şeyi aramayı içeren bir tarama olarak adlandırır ve diğer zamanlarda da arama olarak adlandırır. Ne olduğunu anlamak için her zaman satır sayısına bakmam gerekir. Db2'nin çıktısında açıkça bir 3334 olduğundan, umduğum şeyi yapar. Çok ilginç.
John

Evet, bazen kafa karıştırıcı buluyorum. Neler olup bittiğini gerçekten anlamak için her operatör için daha ayrıntılı bilgilere bakmak gerekir.
Lennart

0

Bu özel sorguda, bir CASEifade bile almak çok saçma . Belirli bir vakayı filtreliyorsunuz! Belki de bu, verdiğiniz belirli örnek sorgunun sadece bir ayrıntısıdır, ancak değilse, eşdeğer sonuçlar almak için bu sorguyu yazabilirsiniz:

SELECT
    [Extent1].[Number] AS [Number],
    'one' AS [Name]
FROM [dbo].[MyEntities] AS [Extent1]
WHERE [Extent1].[Number] = 0;

Bu size tam olarak aynı sonuç kümesini verecektir ve zaten bir CASEifadede zaten sabit kodlama değerleri olduğundan, burada herhangi bir sürdürülebilirliği kaybetmezsiniz.


1
Bence noktayı kaçırıyorsunuz - bu, dize gösterimleri aracılığıyla numaralandırmalarla çalışan bir arka uç kod tabanından SQL üretildi. SQL'i yansıtan kod sorguya şiddet uyguluyor. Eminim ki, eğer SQL kendisi yazıyor olsaydı, daha iyi bir sorgu yazabilirdi. Bu nedenle, hiç bir CASEifadeye sahip olmak aptalca değil , çünkü ORM'ler böyle bir şey yapıyorlar. Aptal olan şey, sorunun bu basit yönlerini tanımamış olmanız ... (dolaylı olarak beyinsiz olarak adlandırılmak için bu nasıl?)
ErikE

@ErikE Yine de aptalca, çünkü numaralandırmanın sayısal değerini kullanabileceğiniz için , zaten C # varsayalım. (SQL Server'dan bahsettiğimiz göz önüne alındığında oldukça güvenli bir varsayım.)
jpmc26

Ancak kullanım durumunun ne olduğu hakkında hiçbir fikriniz yok. Belki de sayısal değere geçmek büyük bir değişiklik olurdu. Belki de numaralandırmalar mevcut bir dev kod tabanına uyarlandı. Bilgi olmadan eleştirmek çok saçma.
ErikE

@ErikE Saçma, neden yapıyorsun? =) Sadece, kullanım örneği sorudaki örnek kadar basitse (cevabımın önsözünde açıkça belirtilir), CASEifadenin dezavantaj olmadan tamamen ortadan kaldırılabileceğini belirtmek için cevap verdim . Of tabii orada bilinmeyen faktörler olabilir, ancak bunlar belirtilmemiş konum olabilir.
jpmc26

Cevabınızın olgusal kısımlarına hiçbir itirazım yok, sadece öznel olarak karakterize edilen kısımlara. Bilgi olmadan eleştirip eleştirmeme gelince, titizlikle temiz mantığı kullanamadığım veya açıkça yanlış olan varsayımlar yaptığım herhangi bir yolu anlamak için kulaklarım ...
ErikE
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.