sayfalamayı uygulamanın verimli yolu


118

Sayfalama için LINQ'leri Skip()ve Take()yöntemini kullanmalı mıyım yoksa kendi sayfamı bir SQL sorgusuyla uygulamalı mıyım?

Hangisi en verimli? Neden birini diğerine tercih edeyim?

SQL Server 2008, ASP.NET MVC ve LINQ kullanıyorum.


Sanırım değişir. Hangi uygulama kutusu üzerinde çalışıyorsun? ne tür bir yükü olacak?
BuddyJoe

Bu yanıta da bir göz atın: stackoverflow.com/a/10639172/416996
Õzbek

Şuna da bir göz atın aspsnippets.com/Articles/…
Frank

Yanıtlar:


175

Şüphenize kısa bir cevap vermeye çalışıyorum, eğer skip(n).take(m)metotları linq üzerinde (veritabanı sunucusu olarak SQL 2005/2008 ile) çalıştırırsanız, sorgunuz Select ROW_NUMBER() Over ...SQL motorunda bir şekilde doğrudan sayfalama ile ifadeyi kullanacaktır .

Size bir örnek vermek gerekirse, adında bir db tablom var mtcityve aşağıdaki sorguyu yazdım (linq to varlıklarla da çalışın):

using (DataClasses1DataContext c = new DataClasses1DataContext())
{
    var query = (from MtCity2 c1 in c.MtCity2s
                select c1).Skip(3).Take(3);
    //Doing something with the query.
}

Ortaya çıkan sorgu şöyle olacaktır:

SELECT [t1].[CodCity], 
    [t1].[CodCountry], 
    [t1].[CodRegion], 
    [t1].[Name],  
    [t1].[Code]
FROM (
    SELECT ROW_NUMBER() OVER (
        ORDER BY [t0].[CodCity], 
        [t0].[CodCountry], 
        [t0].[CodRegion], 
        [t0].[Name],
        [t0].[Code]) AS [ROW_NUMBER], 
        [t0].[CodCity], 
        [t0].[CodCountry], 
        [t0].[CodRegion], 
        [t0].[Name],
        [t0].[Code]
    FROM [dbo].[MtCity] AS [t0]
    ) AS [t1]
WHERE [t1].[ROW_NUMBER] BETWEEN @p0 + 1 AND @p0 + @p1
ORDER BY [t1].[ROW_NUMBER]

Pencereli bir veri erişimi olan (oldukça havalı, btw cuz en başından beri verileri döndürür ve koşullar karşılandığı sürece tabloya erişir). Bu şuna çok benzer olacaktır:

With CityEntities As 
(
    Select ROW_NUMBER() Over (Order By CodCity) As Row,
        CodCity //here is only accessed by the Index as CodCity is the primary
    From dbo.mtcity
)
Select [t0].[CodCity], 
        [t0].[CodCountry], 
        [t0].[CodRegion], 
        [t0].[Name],
        [t0].[Code]
From CityEntities c
Inner Join dbo.MtCity t0 on c.CodCity = t0.CodCity
Where c.Row Between @p0 + 1 AND @p0 + @p1
Order By c.Row Asc

Bu ikinci sorgunun linq sonucundan daha hızlı yürütülmesi istisnai olarak, veri erişim penceresini oluşturmak için yalnızca indeksi kullanacaktır; Bu, bazı filtrelemeye ihtiyacınız varsa, filtrelemenin Varlık listesinde (satırın oluşturulduğu yer) olması (veya olması gerekir) ve iyi performansı sürdürmek için bazı dizinlerin de oluşturulması gerektiği anlamına gelir.

Şimdi, daha iyi olan ne?

Mantığınızda oldukça sağlam bir iş akışınız varsa, doğru SQL yöntemini uygulamak karmaşık olacaktır. Bu durumda LINQ çözüm olacaktır.

Mantığın bu kısmını doğrudan SQL'e indirebilirseniz (bir saklı yordamda), daha da iyi olacaktır çünkü size gösterdiğim ikinci sorguyu (dizinleri kullanarak) uygulayabilir ve SQL'in Yürütme Planını oluşturmasına ve sorgu (performansı artırma).


2
Güzel cevap - yaygın tablo ifadesi, sayfalama yapmanın iyi bir yoludur.
Jarrod Dixon

Soruma bakar mısınız ( stackoverflow.com/questions/11100929/… )? EDMX'ime eklediğim bir SP yaptım ve bunu bir varlıklar arası bağlantı sorgusunda kullandım.
Misi

2
+1, iyi yanıt, ikinci örneğin performans faydalarını açıkladığınız için teşekkür ederim
Cohen

@Johan: Büyük sayfa numaraları için ofsetlerden çok daha iyi performans gösteren arama yöntemi adında bir alternatif var .
Lukas Eder

50

Kullanmayı dene

FROM [TableX]
ORDER BY [FieldX]
OFFSET 500 ROWS
FETCH NEXT 100 ROWS ONLY

SQL sunucusunda 501'den 600'e kadar olan satırları belleğe yüklemeden almak için. Bu sözdiziminin yalnızca SQL Server 2012 ile kullanılabildiğini unutmayın


Bunun yanlış olduğunu düşünüyorum. Görüntülenen SQL, 502-601 arasındaki satırları gösterir (sıfır indekslemiyorsanız?)
Smudge202

Hayır, 501'den 600'e kadar satır alıyor
Volkan Şen

12

LINQ-to-SQL bir OFFSETyan tümce oluşturacak olsa da (muhtemelen ROW_NUMBER() OVER() başkalarının da bahsettiği gibi taklit edilmiştir ), SQL'de disk belleği gerçekleştirmenin tamamen farklı, çok daha hızlı bir yolu vardır. Bu blog yazısında açıklandığı gibi bu genellikle "arama yöntemi" olarak adlandırılır .

SELECT TOP 10 first_name, last_name, score
FROM players
WHERE (score < @previousScore)
   OR (score = @previousScore AND player_id < @previousPlayerId)
ORDER BY score DESC, player_id DESC

@previousScoreVe @previousPlayerIddeğerler önceki sayfadan son kaydın ilgili değerlerdir. Bu, "sonraki" sayfayı getirmenizi sağlar. Eğer ORDER BYyön ise ASC, sadece kullanım >yerine.

Yukarıdaki yöntemle, önceki 40 kaydı almadan önce 4. sayfaya hemen atlayamazsınız. Ama çoğu zaman zaten o kadar uzağa atlamak istemezsiniz. Bunun yerine, indekslemenize bağlı olarak verileri sabit zamanda alabilen çok daha hızlı bir sorgu elde edersiniz. Ayrıca, verileriniz değişse de değişse de sayfalarınız "sabit" kalır (örn. 1. sayfada, 4. sayfadayken).

Bu, örneğin web uygulamalarında daha fazla veriyi tembel olarak yüklerken sayfalamayı uygulamanın en iyi yoludur.

Not, "arama yöntemi" aynı zamanda tuş takımı sayfalama olarak da adlandırılır .


5

LinqToSql otomatik olarak bir .Skip (N1) .Take (N2) 'yi sizin için TSQL sözdizimine dönüştürecektir. Aslında, Linq'te yaptığınız her "sorgu", aslında arka planda sizin için bir SQL sorgusu oluşturmaktır. Bunu test etmek için, uygulamanız çalışırken SQL Profiler'ı çalıştırın.

Atla / al metodolojisi benim ve okuduklarımdan diğerleri için çok iyi çalıştı.

Merak ettiğim için, Linq'in atlama / alma işleminden daha verimli olduğuna inandığınız ne tür kendi kendine sayfalama sorgunuz var?


4

Bir saklı yordam içinde Dinamik SQL'e sarılmış bir CTE kullanıyoruz (çünkü uygulamamız veri sunucusu tarafında dinamik sıralama gerektiriyor). İsterseniz basit bir örnek verebilirim.

LINQ'nun ürettiği T / SQL'e bakma şansım olmadı. Birisi bir örnek gönderebilir mi?

Ekstra güvenlik katmanına ihtiyaç duyduğumuz için LINQ veya tablolara doğrudan erişim kullanmıyoruz (dinamik SQL kesintileri bunu bir şekilde kabul etti).

Bunun gibi bir şey hile yapmalı. Parametreler, vb. İçin parametreli değerler ekleyebilirsiniz.

exec sp_executesql 'WITH MyCTE AS (
    SELECT TOP (10) ROW_NUMBER () OVER ' + @SortingColumn + ' as RowID, Col1, Col2
    FROM MyTable
    WHERE Col4 = ''Something''
)
SELECT *
FROM MyCTE
WHERE RowID BETWEEN 10 and 20'

2
@mrdenny - Bir örneğin ipucu sağladığınız: With sp_executesqlGüvenli bir şekilde, örneğin parametrelerini geçirmek olanağına sahip: EXECUTE sp_executesql 'WITH myCTE AS ... WHERE Col4=@p1) ...', '@p1 nvarchar(max)', @ValueForCol4. Bu bağlamda güvenli, SQL enjeksiyonuna karşı sağlam olduğu anlamına gelir - değişken içindeki her olası değeri @ValueForCol4bile geçirebilirsiniz - '--'ve sorgu çalışmaya devam eder!
Matt

1
@mrdenny Merhaba, sorguyu birleştirmek yerine şuna benzer bir şey kullanıyoruz: SELECT ROW_NUMBER() OVER (ORDER BY CASE WHEN @CampoId = 1 THEN Id WHEN @CampoId = 2 THEN field2 END)
Ezequiel

Bu, bazı korkunç SQL Yürütme planları üretebilir.
mrdenny

@mrdenny: Büyük sayfa numaraları için arama yöntemiROW_NUMBER() OVER() ofset öykünmesinden çok daha hızlı olabilir . Ayrıca bakınız: 4guysfromrolla.com/webtech/042606-1.shtml
Lukas Eder

2

SQL Server 2008'de:

DECLARE @PAGE INTEGER = 2
DECLARE @TAKE INTEGER = 50

SELECT [t1].*
FROM (
    SELECT ROW_NUMBER() OVER (ORDER BY [t0].[COLUMNORDER] DESC) AS [ROW_NUMBER], [t0].*
    FROM [dbo].[TABLA] AS [t0]
    WHERE ([t0].[COLUMNS_CONDITIONS] = 1)
    ) AS [t1]
WHERE [t1].[ROW_NUMBER] BETWEEN ((@PAGE*@TAKE) - (@TAKE-1)) AND (@PAGE*@TAKE)
ORDER BY [t1].[ROW_NUMBER]

T0'daki tüm kayıtlar t1'deki sadece o sayfaya karşılık gelenlerdir


2

Benim verdiğim yaklaşım, SQL sunucusunun ulaşabileceği en hızlı sayfalandırmadır. Bunu 5 milyon kayıtta test ettim. Bu yaklaşım, SQL Server tarafından sağlanan "YALNIZCA SONRAKİ 10 SIRA KAYDIRIN" dan çok daha iyidir.

-- The below given code computes the page numbers and the max row of previous page
-- Replace <<>> with the correct table data.
-- Eg. <<IdentityColumn of Table>> can be EmployeeId and <<Table>> will be dbo.Employees

DECLARE @PageNumber int=1; --1st/2nd/nth page. In stored proc take this as input param.
DECLARE @NoOfRecordsPerPage int=1000;

 DECLARE @PageDetails TABLE
       (
        <<IdentityColumn of Table>> int,
        rownum int,
        [PageNumber] int
       )           
       INSERT INTO @PageDetails values(0, 0, 0)
       ;WITH CTE AS
       (
       SELECT <<IdentityColumn of Table>>, ROW_NUMBER() OVER(ORDER BY <<IdentityColumn of Table>>) rownum FROM <<Table>>
       )
       Insert into @PageDetails 
       SELECT <<IdentityColumn of Table>>, CTE.rownum, ROW_NUMBER() OVER (ORDER BY rownum) as [PageNumber] FROM CTE WHERE CTE.rownum%@NoOfRecordsPerPage=0


--SELECT * FROM @PageDetails 

-- Actual pagination
SELECT TOP (@NoOfRecordsPerPage)
FROM <<Table>> AS <<Table>>
WHERE <<IdentityColumn of Table>> > (SELECT <<IdentityColumn of Table>> FROM 
@PageDetails WHERE PageNumber=@PageNumber)
ORDER BY <<Identity Column of Table>>

0

performansı daha da artırabilirsiniz, bunu kontrol edin

From CityEntities c
Inner Join dbo.MtCity t0 on c.CodCity = t0.CodCity
Where c.Row Between @p0 + 1 AND @p0 + @p1
Order By c.Row Asc

from öğesini bu şekilde kullanırsanız daha iyi sonuç verecektir:

From   dbo.MtCity  t0
   Inner Join  CityEntities c on c.CodCity = t0.CodCity

nedeni: çünkü, CityEntities tablosunda MtCity'ye katılmadan önce birçok rekoru ortadan kaldıracak olan where sınıfını kullanıyorsunuz, bu yüzden% 100 eminim performansı kat kat artıracak ...

Neyse, rodrigoelp'in cevabı gerçekten yardımcı oldu.

Teşekkürler


Bu tavsiyenin herhangi bir performans etkisi olacağından şüpheliyim. Bunun için bir referans bulunamıyor ancak sorgudaki iç birleştirme sırası gerçek birleştirme sırasından farklı olabilir. İkincisi, tablonun istatistikleri ve işlem maliyeti tahminleri kullanılarak sorgu iyileştirici tarafından kararlaştırılır.
Imre Pühvel

@ImreP: Bu aslında tanımladığım arama yöntemine bir şekilde karşılık gelebilir . Yine de nereden @p0ve daha spesifik olarak @p1nereden geldiğinden emin değilim
Lukas Eder

0

PageIndex'i geçerek sayfalamayı bu basit şekilde uygulayabilirsiniz.

Declare @PageIndex INT = 1
Declare  @PageSize INT = 20

Select ROW_NUMBER() OVER ( ORDER BY Products.Name ASC )  AS RowNumber,
    Products.ID,
    Products.Name
into #Result 
From Products

SELECT @RecordCount = COUNT(*) FROM #Results 

SELECT * 
FROM #Results
WHERE RowNumber
BETWEEN
    (@PageIndex -1) * @PageSize + 1 
    AND
    (((@PageIndex -1) * @PageSize + 1) + @PageSize) - 1

0

2008'de Skip () kullanamıyoruz. Take ()

Yol şudur:

var MinPageRank = (PageNumber - 1) * NumInPage + 1
var MaxPageRank = PageNumber * NumInPage

var visit = Visita.FromSql($"SELECT * FROM (SELECT [RANK] = ROW_NUMBER() OVER (ORDER BY Hora DESC),* FROM Visita WHERE ) A WHERE A.[RANK] BETWEEN {MinPageRank} AND {MaxPageRank}").ToList();
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.