现在的位置: 首页 > 综合 > 正文

SQL SERVER 2005 RowNumber() 惹的祸

2018年05月04日 ⁄ 综合 ⁄ 共 6114字 ⁄ 字号 评论关闭

http://blog.csdn.net/qinchende/article/details/2950272

SQL SERVER 2005 RowNumber() 惹的祸(第一篇)

从昨天开始研究SQL server 2005的最新分页方式RowNumber()发现其不可想象的比纯SQL
Top Max/Min
效率还高(这是在没有类似于Row_numberRowNumber()limit之类的最高效率分页,在之前的日志中有相关的分析),而且在检索较后的数据非常明显。

注:测试环境为Windows 2000 sp4SQL
Server 2005 Enterprise
C4 2.1768RAM

方案一

select top 20 * from [AdventureWorks].[Person].[Contact]

where contactid>=(

         select max(contactid) from(

                   select top 15001 contactid from [AdventureWorks].[Person].[Contact]

                   order by contactid)tbl

         )

order by contactid

方案二

SELECT top 20 * from(

 select *,ROW_NUMBER() over(order by contactid) as rowno

 FROM [AdventureWorks].[Person].[Contact] l)tbl

 where rowno>15000

 order by contactid;

以上两者查询开销之比竟然是919!!!以下是多次执行后较稳定时的结果,方案一以3次微弱优势胜出。

(插花:SQL server的缓存功能太强,连续读数时CPU时间和操作时间跳动很大,难以作参考,只能以稳定的逻辑读取作为比较数据,成本预算也算是另一种的参考数据)

在开始记录在15000

'Contact'。扫描计数1,逻辑读取424 次。

'Contact'。扫描计数2,逻辑读取427 次。

15000倒序时:

 'Contact'。扫描计数 1,逻辑读取 422 次。

 'Contact'。扫描计数 2,逻辑读取 425 次。

20倒序时:

 'Contact'。扫描计数 1,逻辑读取 4 次。

 'Contact'。扫描计数 2,逻辑读取 7 次。

SQL server 2005的联机从书中用CTE的方法也类似:

方案三

WITH OrderedOrders AS

(SELECT *,

ROW_NUMBER() OVER (order by contactid)as RowNumber

FROM [AdventureWorks].[Person].[Contact] where middlename is null)

SELECT *-- top 10 *

FROM OrderedOrders

WHERE RowNumber between 1500 and 1510 --and lastname like 'a%';

--where RowNumber>1500 --and Rownumber<1510

order by rownumber

通过估计执行计划方案一和方案三查询开销之比为3763。不过问题出在估算的返回行大小不同。执行结果实际行数是一样的,在这种情况下开销估算也就失去了意义。

在一天的不断实验和修改认识后,基本上对Row_Number分页总结出结论:

1Top方式、Between方式还有是否使用CTE对逻辑读取无明显影响*

*倒序排序时,Top+聚集索引的时间最短,其余的组合基本上高一个数量级以上。Top+rownumber会使逻辑读取暴升!

Top 方式

SELECT top 30 * from(

         select *,ROW_NUMBER() over(order by contactid) as rowno

         FROM [AdventureWorks].[Person].[Contact])tbl

         where rowno>20

         order by contactid;

between方式

SELECT * from(

         select *,ROW_NUMBER() over(order by contactid) as rowno

         FROM [AdventureWorks].[Person].[Contact] )tb

         where rowno>20 and rowno<50

         order by contactid;

Top+CTE

WITH OrderedOrders AS

(SELECT *,

ROW_NUMBER() OVER (order by contactid)as RowNumber

FROM [AdventureWorks].[Person].[Contact])

SELECT  top 30 *

FROM OrderedOrders

where RowNumber>20

order by rownumber;

Between+CTE

WITH OrderedOrders AS

(SELECT *,

ROW_NUMBER() OVER (order by contactid)as RowNumber

FROM [AdventureWorks].[Person].[Contact])

SELECT  *

FROM OrderedOrders

where RowNumber>20 and RowNumber<50

order by RowNumber;

开始为20时:(缓存影响严重,CPU数据忽略)

 'Contact'。扫描计数 1,逻辑读取 7 

 'Contact'。扫描计数 1,逻辑读取 7 

 'Contact'。扫描计数 1,逻辑读取 7 

 'Contact'。扫描计数 1,逻辑读取 7 次,

开始为15000:(缓存影响严重,CPU数据忽略)

 'Contact'。扫描计数 1,逻辑读取 424 

 'Contact'。扫描计数 1,逻辑读取 424 次。

 'Contact'。扫描计数 1,逻辑读取 424 次。

 'Contact'。扫描计数 1,逻辑读取 424 次。

2、单以查询开销分析而论:TopBetween的估计查询开销,受数据量大小、查询位置(修改row>n和用不同表)影响不大。下面会有实例验证。(比较1

Top n 查询时聚集索引扫描估计行数为n*10/3between时固定为100(就算between
1500 and 15000
)还是一样。在返回少量数据时不论是否有where条件Top效率比Between要高,主要是运算符开销小了很多,见比较4(Top的运算符开销随估计行数上升而减幅上升?)返回30数据的时候就一样了(30*10/3=100)。但由于查询开销分析并不以实际数据作计算,因此参考意义有限。

比较一: 不同表不同位置的比较

--19000+             19%

SELECT top 10 * from(

         select *,ROW_NUMBER() over(order by contactid) as rowno

         FROM [AdventureWorks].[Person].[Contact] )tbl

         where rowno>150

         order by contactid;

--19000+             19%

SELECT top 10 * from(

         select *,ROW_NUMBER() over(order by contactid) as rowno

         FROM [AdventureWorks].[Person].[Contact] )tb

         where rowno>15000 --and lastname like 'a%'

         order by contactid;

--19000+             26%

WITH OrderedOrders AS

(SELECT *,

ROW_NUMBER() OVER (order by contactid)as RowNumber

FROM [AdventureWorks].[Person].[Contact])

SELECT *

FROM OrderedOrders

WHERE RowNumber between 1500 and 1510;

order by rownumber

--19000+             17%

SELECT top 10 * from(

         select *,ROW_NUMBER() over(order by addressid) as rowno

         FROM [AdventureWorks].[Person].[address])tbl

         where rowno>150

         order by addressid

--500+                 18%

SELECT top 10 * from(

         select *,ROW_NUMBER() over(order by productid) as rowno

         FROM [AdventureWorks].[production].[product])tbl

         where rowno>150

         order by productid

前三个同用一个表,14个字段,第四个8个字段,第五个25个字段,具体可以看看SQL server 2005的示例数据库AdventureWorks

比较二: 2727273313

Top方式的条件表示式写在什么地方都一样的,用CTE也一样

SELECT top 10 * from(

     select *,ROW_NUMBER() over(order by contactid) as rowno

     FROM [AdventureWorks].[Person].[Contact] where middlename is null and lastname like 'a%')tbl

     where rowno>1500

     order by contactid;

SELECT top 10 * from(

     select *,ROW_NUMBER() over(order by contactid) as rowno

     FROM [AdventureWorks].[Person].[Contact] )tb

     where rowno>1500 and middlename is null and lastname like 'a%'

     order by contactid;

SELECT top 10 * from(

     select *,ROW_NUMBER() over(order by contactid) as rowno

     FROM [AdventureWorks].[Person].[Contact] where middlename is null)tbl

     where rowno>1500 and lastname like 'a%'

     order by contactid;

SELECT top 10 * from(

     select *,ROW_NUMBER() over(order by contactid) as rowno

     FROM [AdventureWorks].[Person].[Contact] where middlename is null)tbl

     where rowno>1500

     order by contactid;

SELECT top 10 * from(

     select *,ROW_NUMBER() over(order by contactid) as rowno

     FROM [AdventureWorks].[Person].[Contact] )tb

     where rowno>1500 and middlename is null

     order by contactid;

SELECT top 10 * from(

     select *,ROW_NUMBER() over(order by contactid) as rowno

     FROM [AdventureWorks].[Person].[Contact] )tb

     where rowno>1500 and lastname like 'a%'

     order by contactid;

聚集索引扫描成本几乎没有区别,前三者的返回的行数区别很大,但对子树大小没有什么影响

比较四:313139(四舍五入的结果吧,我都不知道为什么执行计划会算出101的,但是的确是这样)

SELECT top 20 * from(

     select *,ROW_NUMBER() over(order by contactid) as rowno

     FROM [AdventureWorks].[Person].[Contact] )tb--where middlename is null)tbl

     where rowno>1500 and middlename is null

     order by contactid;

WITH OrderedOrders AS

(SELECT *,

ROW_NUMBER() OVER (order by contactid)as RowNumber

FROM [AdventureWorks].[Person].[Contact]and middlename is null)

SELECT  top 20 *

FROM OrderedOrders

where RowNumber>1500

order by rownumber;

WITH OrderedOrders AS

(SELECT *,

ROW_NUMBER() OVER (order by contactid)as RowNumber

FROM [AdventureWorks].[Person].[Contact] where middlename is null)

SELECT  *

FROM OrderedOrders

WHERE RowNumber between 1501 and 1520

order by rownumber;

PS.一整天用了很多时间在查询成本分析上面,但渐渐发现估算的结果不符合实际。最后推翻了大部分的分析结果。Row_number的搜索方式总算也搞明白了,(正序时)先是分析有没有Top
n
,那么一开始返回的就是n+m(如果存在where
rownumber>m
的话,没有就是0),否则就分析有没有where
rownumber<m
,有就返回m行。以上操作和子查询的条件一起分析,之后返回记录集到外循环所以成本分析中最接近的Top基本上是空操作,因为一早就在聚集扫描中完成了。除非主查询加个Or吧,不然应该都是这样优化的。

抱歉!评论已关闭.