从昨天开始研究SQL server 2005的最新分页方式RowNumber()发现其不可想象的比纯SQL Top Max/Min效率还高(这是在没有类似于Row_number或RowNumber()、limit之类的最高效率分页,在之前的日志中有相关的分析),而且在检索较后的数据非常明显。
注:测试环境为Windows 2000 sp4,SQL Server 2005 Enterprise,C4 2.1,768RAM
方案一
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;
以上两者查询开销之比竟然是91:9!!!以下是多次执行后较稳定时的结果,方案一以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
通过估计执行计划方案一和方案三查询开销之比为37:63。不过问题出在估算的返回行大小不同。执行结果实际行数是一样的,在这种情况下开销估算也就失去了意义。
在一天的不断实验和修改认识后,基本上对Row_Number分页总结出结论:
1、Top方式、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、单以查询开销分析而论:Top和Between的估计查询开销,受数据量大小、查询位置(修改row>n和用不同表)影响不大。下面会有实例验证。(比较1)
Top n 查询时聚集索引扫描估计行数为n*10/3,between时固定为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
比较二: 27:27:27:3:3:13
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;
聚集索引扫描成本几乎没有区别,前三者的返回的行数区别很大,但对子树大小没有什么影响
比较四:31:31:39(四舍五入的结果吧,我都不知道为什么执行计划会算出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