您的位置:首页 > 理论基础 > 计算机网络

sql 分页(原:http://www.cnblogs.com/fly_zj/archive/2010/07/06/1772536.html)

2015-07-13 09:35 555 查看

SQLSERVER2005分页脚本性能实测

网上有很多的分页T-SQL代码,分散在各处,主要的问题是:测试时数据量太小,最多只有2万多条,不同方法的体现出性能差别有疑惑,另外当初在学习sqlserver2005时,一位同学信誓旦旦说分页

在SQLSERVER2005中可以使用EXCEPT关键字,性能最好,理由是EXCEPT是集合运算。当时信以为真。工作以后,发现在SQLSERVER2005中的分页存储过程都没有用到EXCEPT方法,就更疑惑了。

这次系统的看《InsideMicrosoft®SQLServer™2005T-SQLQuerying》这本书时,发现有个创建数据库脚本,数据时随机的,把它作为测试数非常不错,脚本如下(稍微做调整):

--在我电脑上该数据库的创建持续1分钟多
SETNOCOUNTON;
USEmaster;
GO
IFDB_ID('Performance')ISNOTNULL
DROPDATABASEPerformance;
ELSE
CREATEDATABASEPerformance;
GO
USEPerformance;
GO

--创建辅助表Nums,1百万行数据
IFOBJECT_ID('dbo.Nums')ISNOTNULL
DROPTABLEdbo.Nums;
GO
CREATETABLEdbo.Nums(nINTNOTNULLPRIMARYKEY);
DECLARE@maxASINT,@rcASINT;
SET@max=1000000;
SET@rc=1;

INSERTINTONumsVALUES(1);
WHILE@rc*2<=@max
BEGIN
INSERTINTOdbo.NumsSELECTn+@rcFROMdbo.Nums;
SET@rc=@rc*2;
END

INSERTINTOdbo.Nums
SELECTn+@rcFROMdbo.NumsWHEREn+@rc<=@max;
GO

--如果存在dbo.Orders表则删除
IFOBJECT_ID('dbo.Orders')ISNOTNULL
DROPTABLEdbo.Orders;
GO

--定义写变量,以此来创建随机的数据,不明白就忽略算了

DECLARE
@numordersASINT,
@numcustsASINT,
@numempsASINT,
@numshippersASINT,
@numyearsASINT,
@startdateASDATETIME;

SELECT
@numorders=1000000,
@numcusts=20000,
@numemps=500,
@numshippers=5,
@numyears=4,
@startdate='20030101';

--创建Orders表
CREATETABLEdbo.Orders
(
orderidINTNOTNULL,
custidCHAR(11)NOTNULL,
empidINTNOTNULL,
shipperidVARCHAR(5)NOTNULL,
orderdateDATETIMENOTNULL,
fillerCHAR(155)NOTNULLDEFAULT('a')
);

--随机的填入一些数据,1百万行数据
INSERTINTOdbo.Orders(orderid,custid,empid,shipperid,orderdate)
SELECTnASorderid,
'C'+RIGHT('000000000'
+CAST(
1+ABS(CHECKSUM(NEWID()))%@numcusts
ASVARCHAR(10)),10)AScustid,
1+ABS(CHECKSUM(NEWID()))%@numempsASempid,
CHAR(ASCII('A')-2
+2*(1+ABS(CHECKSUM(NEWID()))%@numshippers))ASshipperid,
DATEADD(day,n/(@numorders/(@numyears*365.25)),@startdate)
asorderdate
FROMdbo.Nums
WHEREn<=@numorders
ORDERBYCHECKSUM(NEWID());


--为orderid创建主键,为custid,empid添加索引,并包含shipperid,orderdate列
ALTERTABLEdbo.OrdersADD
CONSTRAINTPK_Orders_orderidPRIMARYKEYCLUSTERED(orderid);

CREATEINDEXidx_Orders_custid_empidONdbo.Orders(custid,empid);


第一种分页方法:使用TOP与NOTIN来分页。注意,获取T-SQL脚本运行的时间,单击SSMS工具栏上的【包含客户端统计信息】按钮。


--把SQLSERVER执行计划缓存清空
DBCCFREEPROCCACHE;
DBCCFREESYSTEMCACHE('ALL');


脚本如下:


CREATEPROCEDUREusp_EvaluatePerformanceBy_Top_In
@pagesizeINT,
@pagenumINT
AS

SELECTTOP(@pagesize)o1.orderid,o1.custid,o1.empid
FROMdbo.Orderso1
WHEREo1.orderidNOTIN(
SELECTTOP((@pagenum-1)*@pagesize)o2.orderid
FROMdbo.Orderso2
);


GO

--当读取1万条附近的20条数据时花的时间为
EXECusp_EvaluatePerformanceBy_Top_In@pagesize=20,@pagenum=5000


时间为257.400毫秒






--当读取第20万条附近的20条数据时花的时间为

EXECusp_EvaluatePerformanceBy_Top_In@pagesize=20,@pagenum=10000


时间为:152.700,sqlserver利用了缓存的可执行计划,故时间要少






--当读取第80万条附近的20条数据时花的时间为

EXECusp_EvaluatePerformanceBy_Top_In@pagesize=20,@pagenum=40000

所花的时间为:240.200,同样sqlserver利用了缓存的可执行计划,时间变化不大






总结一下,当利用top和notin来分页,且要查找的列都已在索引中时,

1万条数据附近,为257.400ms

20万数据条附近,为152.700ms

80万条数据附近,为240.200ms

第二种分页方法使用CTE和Row_Number函数,请看如下的T-SQL代码

--清空可执行计划缓存
DBCCFREEPROCCACHE;
DBCCFREESYSTEMCACHE('ALL');

--使用CTE和ROW_NUMBER()来分页
CREATEPROCEDUREusp_EvaluatePerformanceBy_Row_Number
@pagesizeINT,
@pagenumINT
AS

WITHTmpAS
(
SELECT ROW_NUMBER()OVER(ORDERBYorderidASC)AScolnum,
orderid,
custid,
empid
FROMdbo.Orderso1
)
SELECTorderid,custid,empidFROMTmp
WHEREcolnum>(@pagenum-1)*@pagesize
ANDcolnum<=@pagenum*@pagesize;

GO

--当读取1万条附近的20条数据时花的时间为

EXECusp_EvaluatePerformanceBy_Row_Number@pagesize=20,@pagenum=500


所花费的时间为:21.500





--当读取第20万条附近的20条数据时花的时间为

EXECusp_EvaluatePerformanceBy_Row_Number@pagesize=20,@pagenum=10000


所花费的时间为:44.900





--当读取第80万条附近的20条数据时花的时间为

EXECusp_EvaluatePerformanceBy_Row_Number@pagesize=20,@pagenum=40000


所花费的时间为:118.400





总结一下,当利用CTE和ROW_NUMBER来分页,且要查找的列都在索引中时,

1万条数据附近,为21.500ms

20万数据条附近,为44.900ms

80万条数据附近,为118.400ms

第三种分页的方法是使用EXCEPT,请看如下的T-SQL代码

--清空可执行计划缓存
DBCCFREEPROCCACHE;
DBCCFREESYSTEMCACHE('ALL');

--利用except来求分页

CREATEPROCEDUREusp_EvaluatePerformanceBy_Except
@pagesizeINT,
@pagenumINT
AS
SELECTTOP(@pagesize*@pagenum)orderid,
custid,
empid
FROMdbo.Orders
EXCEPT(
SELECTTOP((@pagenum-1)*@pagesize)

orderid,
custid,
empid
FROMdbo.Orders
);
GO

--当读取第1万条附近的20条数据时花的时间为

EXECusp_EvaluatePerformanceBy_Except@pagesize=20,@pagenum=500
所需的时间为:123.000






--当读取第20万条附近的20条数据时花的时间为

EXECusp_EvaluatePerformanceBy_Except@pagesize=20,@pagenum=10000
所花时间为:174.600






--当读取第80万条附近的20条数据时花的时间为

EXECusp_EvaluatePerformanceBy_Except @pagesize=20,@pagenum=40000
所花时间为:390.200






总结一下,当利用EXCEPT来分页,且要查找的列都在索引中时,

1万条数据附近,为123.000ms

20万数据条附近,为174.600ms

80万条数据附近,为390.200ms

第四种分页的方法是利用TOP和Max函数结合,请看如下的T-SQL代码


--清空可执行计划缓存
DBCCFREEPROCCACHE;
DBCCFREESYSTEMCACHE('ALL');

--第总方法通过top和max来求值
CREATEPROCEDUREusp_EvaluatePerformanceBy_Top_Max
@pagesizeINT,
@pagenumINT
AS

SELECTTOP(@pagesize)orderid,custid,empid
FROMdbo.Orderso1
WHEREo1.orderid>(
SELECTmax(d.orderid)asnumFROM
(
SELECTtop((@pagenum-1)*@pagesize)orderid
FROMdbo.orderso2ORDERBYorderid
)ASd

)
GO

--当读取第1万条附近的20条数据时花的时间为

EXECusp_EvaluatePerformanceBy_Top_Max@pagesize=20,@pagenum=500
时间为:365.100






--当读取第20万条附近的20条数据时花的时间为

EXECusp_EvaluatePerformanceBy_Top_Max@pagesize=20,@pagenum=10000
时间为:319.800






--当读取第80万条附近的20条数据时花的时间为

EXECusp_EvaluatePerformanceBy_Top_Max@pagesize=20,@pagenum=40000
所花的时间为:137.000






总结一下,当利用TOP和MAX来分页,且要查找的列都在索引中时,

1万条数据附近,为365.100ms

20万数据条附近,为319.800ms

80万条数据附近,为137.000ms

看一看最后的结论:

TOP与NOTIN

TOP与MAX

ROW_NUMBER()

EXCEPT

1万条数据

257.400ms

365.100ms

21.500ms

123.000ms

20万条数据

152.700ms

319.800ms

44.900ms

174.600ms

80万条数据

240.200ms

137.000ms

118.400ms

390.200ms

在SQLSERVER2005中优先选择ROW_NUMBER()方法来分页,在SQLSERVER2000中优先选择TOP和NOTIN方法!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: