OFFSET… FETCH ve eski stil ROW_NUMBER şeması arasında neden yürütme planı farklılıkları var?


15

OFFSET ... FETCHSQL Server 2012 ile sunulan yeni model, basit ve daha hızlı disk belleği sunar. İki formun anlamsal olarak aynı ve çok yaygın olduğu düşünüldüğünde neden farklılıklar var?

Optimize edicinin her ikisini de tanıdığı ve (önemsizce) sonuna kadar optimize ettiği varsayılabilir.

OFFSET ... FETCHMaliyet tahminine göre ~ 2 kat daha hızlı olan çok basit bir durum .

SELECT * INTO #objects FROM sys.objects

SELECT *
FROM (
    SELECT *, ROW_NUMBER() OVER (ORDER BY object_id) r
    FROM #objects
) x
WHERE r >= 30 AND r < (30 + 10)
    ORDER BY object_id

SELECT *
FROM #objects
ORDER BY object_id
OFFSET 30 ROWS FETCH NEXT 10 ROWS ONLY

Ofset-fetch.png

Bu test durumu, üzerinde bir CI oluşturarak object_idveya filtreler ekleyerek değiştirilebilir, ancak tüm plan farklılıklarını kaldırmak imkansızdır. OFFSET ... FETCHyürütme zamanında daha az çalıştığı için her zaman daha hızlıdır.


Çok emin değilim, bu yüzden yorum olarak koyarak, ama sanırım onun çünkü satır numaralandırma ve son sonuç kümesi için koşula göre aynı sıraya sahip. 2. durumda, optimize edici bunu bildiğinden, sonuçları tekrar sıralaması gerekmez. Ancak ilk durumda, dış seçimdeki sonuçların ve iç sonuçtaki satır numaralandırmasının sıralandığından emin olunması gerekir. #Objects üzerinde uygun bir dizin oluşturmak sorunu çözmelidir
Akash,

Yanıtlar:


13

Sorudaki örnekler aynı sonuçları vermez ( OFFSETörneğin, birer birer hata vardır). Aşağıdaki güncellenmiş formlar bu sorunu düzeltir, ROW_NUMBERvaka için fazladan sıralamayı kaldırır ve çözümü daha genel hale getirmek için değişkenleri kullanır:

DECLARE 
    @PageSize bigint = 10,
    @PageNumber integer = 3;

WITH Numbered AS
(
    SELECT TOP ((@PageNumber + 1) * @PageSize) 
        o.*,
        rn = ROW_NUMBER() OVER (
            ORDER BY o.[object_id])
    FROM #objects AS o
    ORDER BY 
        o.[object_id]
)
SELECT
    x.name,
    x.[object_id],
    x.principal_id,
    x.[schema_id],
    x.parent_object_id,
    x.[type],
    x.type_desc,
    x.create_date,
    x.modify_date,
    x.is_ms_shipped,
    x.is_published,
    x.is_schema_published
FROM Numbered AS x
WHERE
    x.rn >= @PageNumber * @PageSize
    AND x.rn < ((@PageNumber + 1) * @PageSize)
ORDER BY
    x.[object_id];

SELECT
    o.name,
    o.[object_id],
    o.principal_id,
    o.[schema_id],
    o.parent_object_id,
    o.[type],
    o.type_desc,
    o.create_date,
    o.modify_date,
    o.is_ms_shipped,
    o.is_published,
    o.is_schema_published
FROM #objects AS o
ORDER BY 
    o.[object_id]
    OFFSET @PageNumber * @PageSize - 1 ROWS 
    FETCH NEXT @PageSize ROWS ONLY;

ROW_NUMBERPlan tahmini maliyeti vardır 0.0197935 :

Satır Sayı Planı

OFFSETPlan tahmini maliyeti vardır 0.0196955 :

Ofset Planı

Bu, 0.000098 tahmini maliyet biriminden tasarruf sağlar ( OFFSETher bir satır için bir satır numarası döndürmek istiyorsanız , plan ekstra işleçler gerektirecektir). OFFSETPlan hala, genel anlamda biraz daha ucuz olacak, ama tahmini maliyeti tam olarak bu olduğunu hatırlıyorum - Gerçek test hala gereklidir. Her iki plandaki maliyetin büyük kısmı, girdi kümesinin tümünün maliyetidir, bu nedenle yardımcı dizinler her iki çözüme de fayda sağlayacaktır.

Sabit değişmez değerlerin kullanıldığı yerlerde (örneğin OFFSET 30orijinal örnekte), optimizer, tam sıralama ve ardından Top yerine yerine bir TopN Sıralaması kullanabilir. TopN ihtiyaç duyduğu satırlar Sıralama sabit bir gerçek ve <= 100 (toplamı olduğunda OFFSETve FETCH) yürütme motoru kullanarak farklı bir sıralama algoritması sıralama genelleştirilmiş TopN daha hızlı gerçekleştirebilir. Her üç durum da genel olarak farklı performans özelliklerine sahiptir.

Optimize edicinin ROW_NUMBERsözdizimi desenini otomatik olarak kullanmak için neden değiştirmediğine gelince OFFSET, bunun birkaç nedeni vardır:

  1. Mevcut tüm kullanımlara uygun bir dönüşüm yazmak neredeyse imkansız
  2. Bazı çağrı sorgularının otomatik olarak dönüştürülmesi ve başkalarının kafa karıştırıcı olmaması
  3. OFFSETPlan her durumda daha iyi olması garanti edilmez

Yukarıdaki üçüncü nokta için bir örnek, çağrı kümesinin oldukça geniş olduğu bir yerde meydana gelir. Kümelenmemiş bir dizin kullanarak gerekli anahtarları aramak ve dizini OFFSETveya ile taramaya kıyasla kümelenmiş dizine manuel olarak bakmak çok daha verimli olabilir ROW_NUMBER. Orada değerlendirmek üzere ek sorunlar çağrıların uygulaması toplamda kaç satır veya sayfa bilmek gerekiyorsa. Burada 'anahtar arama' ve 'dengeleme' yöntemlerinin göreceli esası hakkında iyi bir tartışma var .

Genel olarak, insanların OFFSETayrıntılı sorgulamadan sonra, uygunsa, sayfalama sorgularını değiştirmek için bilinçli bir karar vermeleri daha iyidir .


1
Dolayısıyla, yaygın durumlarda dönüşümün yapılmamasının nedeni, kabul edilebilir bir mühendislik ödünleşimi bulmanın çok zor olmasıdır. Bunun neden böyle olabileceğine dair iyi sebepler sundunuz .; Bunun iyi bir cevap olduğunu söylemeliyim. Birçok anlayış ve yeni düşünceler. Soruyu biraz açık bırakacağım ve sonra en iyi cevabı seçeceğim.
usr

5

Sorgunuzla ilgili olarak, eşit bir maliyet tahmini (50/50) ve eşit G / Ç istatistikleri elde ediyorum :

; WITH cte AS
(
    SELECT *, ROW_NUMBER() OVER (ORDER BY object_id) r
    FROM #objects
)
SELECT *
FROM cte
WHERE r >= 30 AND r < 40
ORDER BY r

SELECT *
FROM #objects
ORDER BY object_id
OFFSET 30 ROWS FETCH NEXT 10 ROWS ONLY

Bu, ryerine sıralayarak sürümünüzde görünen ek sıralamayı önler object_id.


Bu içgörü için teşekkürler. Şimdi bu düşünüyorum düşünüyorum optimizer önce ROW_NUMBER çıktı sıralı doğasını anlamak görmedim. Kümenin object_id tarafından sırasız olduğunu düşünür. Ya da en azından hem r'ye hem de object_id'e göre sıralanmamış.
usr

2
@usr ROW_NUMBER () tarafından kullanılan ORDER BY, sayıları nasıl atayacağını tanımlar. Çıktı sırasına söz vermek için hiçbir şey yapmaz - bu ayrıdır. Öyle olur ki, genellikle çakışır, ancak garanti edilmez.
Aaron Bertrand

@AaronBertrand ROW_NUMBER'in çıktıyı sipariş etmediğini anlıyorum. ROW_NUMBER çıkışı ile aynı sütunlara göre sıralanır Ama eğer aynı sırada olduğu doğru, garantili? Böylece sorgu optimize edici bu gerçeği kullanabilir. Bu nedenle , bu sorguda her zaman iki sıralama işlemi gerekmez.
usr

1
@usr, optimize edicinin hesaba katmadığı yaygın bir kullanım senaryosuna vurdunuz, ancak tek kullanım durumu bu değil . ROW_NUMBER () içindeki siparişin bu sütun ve başka bir şey olduğu durumları düşünün. Veya dış sipariş başka bir sütunda ikincil sıralama yapar. Veya azalan sipariş vermek istediğinizde. Veya tamamen başka bir şeyle. Ben rsadece bir iç içe sorguda ne yapacağım ve bir ifade tarafından sipariş eşleşmesi nedeniyle, temel sütun yerine ifade tarafından sipariş gibi - Ifade tekrarlamak yerine ifadeye atanan diğer adı kullanır.
Aaron Bertrand

4
@usr Ve Paul'un noktasına gelince, optimizatörde işlevsellik boşluklarını bulabileceğiniz durumlar olacak. Düzeltilmeyecekse ve sorguyu yazmanın daha iyi bir yolunu biliyorsanız, daha iyi bir yol kullanın. Hasta: "Doktor, x yaptığımda acıyor." Doktor: "Yapma x." :-)
Aaron Bertrand

-3

Bu özelliği eklemek için sorgu optimize edicisini değiştirdiler. Yani özellikle ofset ... getirme komutunu desteklemek için mekanizmalar uyguladılar. Başka bir deyişle, en iyi sorgu için SQL Server çok daha fazla iş yapmak zorunda. Böylece sorgu planlarındaki fark.

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.