• 数据库端分页优化


    --最近看到Paul White写的一篇数据库端分页优化的文章,感觉不错。所以把主要内容意译出来,作为分享。

    概要

    在SQL Server 2005及以后版本支持ROW_NUMBER函数。ROW_NUMBER函数可以很方便地用于数据库端分页。本文针对使用此函数进行分页的方法,进行优化和讨论。

    需求场景

    首先,我们假设一个需求场景,需要进行分页的数据集的宽度很大(每行数据的字段多,长度大)。可使用以下代码来模拟此表:

    --创建测试表
    create table Post
    (
    	post_id int identity not null,
    	thread_id int not null,
    	member_id int not null,
    	create_dt datetime not null,
    	title nvarchar(100) not null,
    	body nvarchar(2500) not null,
    	constraint PK_Post_post_id
    		primary key clustered(post_id)
    );
    go
    --填充10000条随机数据
    With Numbers as
    (
    	select top(10000)
    		row_number() over(order by (select 0)) as n
    	from
    		master.sys.columns C1,
    		master.sys.columns C2,
    		master.sys.columns C3
    )
    insert	Post
    	(thread_id, member_id, create_dt, title, body)
    select
    	abs(checksum(newid()))%16 + 1 as thread_id,
    	abs(checksum(newid()))%16384 + 1 as member_id,
    	dateadd(minute, Numbers.n * 60, '20020901') as create_dt,
    	replicate(nchar(rand(checksum(newid()))*26 + 65), rand(checksum(newid()))*70 + 30) as title,
    	replicate(nchar(rand(checksum(newid()))*26 + 65), rand(checksum(newid()))*1750 + 750) as body
    from
    	Numbers;

    我们后面的工作是从这张表里返回分页的数据集。客户端将提供页码和每页的行数。

    初始解决方案

    对于此问题的一个解决方案是使用通用表表达式(CTE),代码如下:

    declare @PageNumber int;
    declare @PageSize int;
    
    set @PageNumber = 10;
    set @PageSize = 50;	
    	
    With Paging as
    (
    	select
    		ROW_NUMBER() over(order by P.post_id asc) as rn,
    		post_id,
    		thread_id,
    		member_id,
    		create_dt,
    		title,
    		body
    	from
    		Post P
    )
    select top(@PageSize)
    	PG.rn,
    	PG.post_id,
    	PG.thread_id,
    	PG.member_id,
    	PG.create_dt,
    	PG.title,
    	PG.body
    from
    	Paging PG
    where
    	PG.rn > (@PageNumber * @PageSize) - @PageSize;

    运行以上代码所生成的执行计划如下:

    看上去这是一个简单且高效的执行计划,但随着@PageNumber增大,扫描一个宽表很快就会变得昂贵。我们分别取@PageNumber等于1,10,50,100,200来看一下此查询的logical reads。打开SET STATISTICS IO ON,并在每次查询前清除缓存:

    DBCC DROPCLEANBUFFERS;
    DBCC FREESYSTEMCACHE ('ALL');

    运行所得结果如下表所示:

    Page Number 1 10 50 100 200
    logical reads 29 282 1390 2793 5590

    聚集索引扫描成本的快速增长归因于单行数据较大。虽然聚集索引的键post_id很窄,只有4byte,但聚集索引扫描却需要逐一扫描每行所有数据的页。

    替代解决方案

    我们可以考虑只在post_id列上创建非聚集索引,这个新索引很窄,使执行计划快速找到需要的页,之后再查找剩余列的数据。使用如下代码来创建此索引:

    create unique nonclustered index un_idx_Post_post_id
    on Post(post_id asc)

    我可以按如下3步来重写上面的查询:

    1. 对非聚集索引进行部分扫描,添加row numbers

    2. 过滤数据行得到我们所要的数据集

    3. 对步骤2中的50条结果进行lookup,得到其他列的数据组成最后的结果

    此解决方案的代码如下:

    declare @PageNumber int;
    declare @PageSize int;
    
    set @PageNumber = 10;
    set @PageSize = 50;
    
    With Keys as
    (
    	--为最少的行加上行号
    	select top(@PageNumber * @PageSize)
    		row_number() over(order by P1.post_id asc) as rn,
    		P1.post_id
    	from
    		Post P1
    	order by
    		P1.post_id asc
    ),
    SelectedKeys as
    (
    	--获得需要数据集的主键
    	select top(@PageSize)
    		SK.rn,
    		SK.post_id
    	from
    		Keys SK
    	where
    		SK.rn > ((@PageNumber - 1) * @PageSize)
    	order by
    		SK.post_id ASC
    )
    select --获得每行的其他列
    	SK.rn,
    	P2.post_id,
    	P2.thread_id,
    	P2.member_id,
    	P2.create_dt,
    	P2.title,
    	P2.body	
    from
    	SelectedKeys SK
    	join
    	Post P2
    	on
    		P2.post_id = SK.post_id;

    运行以上代码所生成的执行计划如下:

    KeyLookup1

    同样我们分别取@PageNumber等于1,10,50,100,200来查看此查询的logical reads。打开SET STATISTICS IO ON,并在每次查询前清除缓存:

    DBCC DROPCLEANBUFFERS;
    DBCC FREESYSTEMCACHE ('ALL');

    运行所得结果如下表所示:

    Page Number 1 10 50 100 200
    logical reads 388 390 389 392 397

    实验结论

    比较2种方案的逻辑读数据,可以看出替代方案(键查找方案)可以获得更为平稳和可预期的性能。

    有人还提出过把2种方案进行结合的方案,在获取前几页数据时使用聚集索引扫描,当需要获取之后的数据时切换为键查找。

    (完)

  • 相关阅读:
    java:maven(maven-ssm(聚合,分包开发))
    java:Maven(Maven_ssm)
    java:Mybatis框架3(二级缓存,延时和积极加载,SSI(Ibatis)集成,SSM集成)
    java:LeakFilling (Mybatis)
    java:Mybatis框架2(基于mapper接口的开发,多种查询,复合类型查询,resultMap定义,多表联查,sql片段)
    java:Mybatis框架1(基本配置,log4j,Junit4(单元测试))
    java:Springmvc框架3(Validator)
    java:Springmvc框架2(Ajax,Json,Interceptor,Upload,Exception)
    WebLogic XMLDecoder反序列化漏洞复现
    Struts2-052 漏洞复现
  • 原文地址:https://www.cnblogs.com/DBFocus/p/1750228.html
Copyright © 2020-2023  润新知