Linq To SQL分頁失敗後引發的思考

前言:
從微軟發佈Linq To SQL依此,程序員圍繞其與SqlDataAdapter等相比進行討論,根據CSDN上的報道,LINQ比SqlDataReader落後的速度不超過10%。更加相信微軟對Linq性能的分析會結合算法和統計結果來比較,對於linq查詢性能--博客園的黃昕已經有所分析。於是產生想實驗一下Linq和SqlDataAdapter等分別在大數據量下進行分頁。園子裏的Yzl的研究室已經對Linq分頁可能出現的問題提出一種情況。(以下只是實驗過程並非測試所以沒有benchmark).

實驗步驟:
一、數據庫
數據庫名People
數據表名Prof 包括ID Name Age三個字段
記錄數 10萬條(也許不是很足夠)
下面的數據庫代碼並非最佳方案,大家貼一下自己的

CREATE DATABASE People
ON
Primary
(
    NAME
='people',
    FILENAME
='D:\people_Data.mdf',
    SIZE
=10,
    FILEGROWTH
=10%
)

LOG ON
(
    NAME
='peoplelog',
    FILENAME
='D:\people_Log.ldf',
    SIZE
=10,
    FILEGROWTH
=10%
)
GO
USE People
CREATE TABLE Prof
(
    ID INT IDENTITY(
1,1) NOT NULL,
    Name NVARCHAR(
100) COLLATE Chinese_PRC_CI_AS NULL ,
    Age INT NULL,
)

GO
  SET IDENTITY_INSERT Prof ON
 DECLARE @i INT,@age INT
 SET @i=1
 WHILE @i<=100000
 BEGIN
  SET @age=CAST((RAND()*(100-20)+20)AS INT)
  INSERT INTO Prof(ID,Name,Age) VALUES(@i,'Name_'+ CAST(@age AS NVARCHAR),@age)
  SET @i=@i+1
 END
SET IDENTITY_INSERT Prof OFF

測試代碼一(SQL部分):
網上已經有很多非常好的分頁算法,各人按照設計的需要選擇合適的爲好,特別提一下Thin的算法(很簡潔),測試中採用了李洪根發佈的其中一種分頁算法

//省略行參數設置和拼接
string strcmd = "SELECT TOP 20 * " +
                
"FROM Prof " +
                
"WHERE (ID >" +
                
"(SELECT MAX(ID) FROM (SELECT TOP 60000 id FROM Prof ORDER BY id) AS T))  ORDER BY ID";
顯示查詢耗時:00:00:00.0322245

測試代碼二(Linq部分)
Linq To SQL的分頁主要通過Skip和Take操作符實現,代碼如下:

//省去設置參數
//分頁Skip(PageSize * PageIndex).Take(PageSize)
PeopleDataContext dc = new PeopleDataContext();
var query 
= (from p in dc.Prof select p).Skip(60000).Take(20);
然而卻出現異常:
此提供程序只支持對返回實體或投影(包含所有標識列)的有序查詢使用 Skip(),這種查詢爲單表(非聯接)查詢,或者爲 Distinct、Except、Intersect 或 Union (非 Concat)操作。
第一次遇到這種異常(當然很多朋友並不會,而且已經看出問題所在了),查找Skip的定義
public static IEnumerable<TSource> Skip<TSource>(
    
this IEnumerable<TSource> source,
    
int count
)
此方法通過使用延遲執行實現。即時返回值爲一個對象,該對象存儲執行操作所需的所有信息。只有通過直接調用對象的 GetEnumerator 方法或使用 Visual C# 中的 foreach(或 Visual Basic 中的 For Each)來枚舉該對象時,才執行此方法表示的查詢。
再看一下該查詢生成的SQL代碼:
SELECT TOP 20 [t0].[ID], [t0].[Name], [t0].[Age]
FROM [dbo].[Prof] AS [t0]
WHERE NOT (EXISTS(
    SELECT NULL AS [EMPTY]
    FROM (
        SELECT TOP 
60000 [t1].[ID]
        FROM [dbo].[Prof] AS [t1]
        ) AS [t2]
    WHERE [t0].[ID] 
= [t2].[ID]
    ))
Skip查詢需要數據標識列提供查詢的根據,是否可以假設Skip是通過標識列的唯一性來逐一返回對象的呢?
修改數據庫People表Prof,設置其ID爲主鍵,(上面的數據庫相應修改爲)
 ALTER TABLE Prof
ADD CONSTRAINT PK_ID PRIMARY KEY (ID)
GO

再次運行,顯示查詢耗時:00:00:00.0478485

問題:
1.爲什麼兩次查詢的耗時相差那麼大呢?(估計是個人機器以及代碼問題:))
2.Skip是否通過主鍵的唯一性逐次返回查詢對象?
總結:
SQLServer的執行效率是按照語義來執行的,也許Linq在性能上不一定和SQLDataAdapter等完全一樣,但是在開發效率上,我們可以看出Linq的實現代碼的簡易性是相對較好的,只要克服其中的一些問題,相信Linq會爲以後的數據查詢提供更強大幫助!

備註:
Scott寫了一系列Linq的介紹非常經典
王磊發表的文章中提到過了大量數據分頁的實現,主要用的數據庫爲Northwind而相應的代碼也完善。
Linq性能測試[url]http://www.mbeller.de/2007/12/performance-comparison-between-linq.html[/url]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章