SQL数据库分页原理

SQL数据库分页原理,第1张

要想分页,首先得做好准备工作。你要先声明每页显示多少条数据,还得获取当前选择的是多少页的页码。有了这两个分页就好办了。

sql如下:select top 10 from tableName

where (id not in(select top 20 from tableName order by Id desc)) order by Id desc

每页显示的数量:自己定义。

总页数:数据总条数/每页显示的条数

当前页码的计算方法:(页码-1)*每页显示的数量。比如我要浏览第3页的数据,3从客户端传送过来后,在后台对页码进行处理:(3-1)*每页显示的数量(假如是10).算出来后的结果就是20.你在把20以参数注入的方式动态添加到上面那个20那里就ok了。

sql中的10表示你每页显示的数据,这里跟10,就代表每页显示10条。(你可以定义一个常量作为每页显示的条数)

where中的20表示不包括前面的20条数据,也就是查询出从第21条到30之间的数据。

不知道我这样说你是否理解,其实只要理解了sql语句,分页就很好做了。

1、前言

分页显示是一种非常常见的浏览和显示大量数据的方法,属于web编程中最常处理的事件之一。对于web编程的老手来说,编写这种代码实在是和呼吸一样自然,但是对于初学者来说,常常对这个问题摸不着头绪,因此特地撰写此文对这个问题进行详细的讲解,力求让看完这篇文章的朋友在看完以后对于分页显示的原理和实现方法有所了解。本文适合初学者阅读,所有示例代码均使用php编写。

2、原理

所谓分页显示,也就是将数据库中的结果集人为的分成一段一段的来显示,这里需要两个初始的参数:

每页多少条记录($PageSize)?

当前是第几页($CurrentPageID)?

现在只要再给我一个结果集,我就可以显示某段特定的结果出来。

至于其他的参数,比如:上一页($PreviousPageID)、下一页($NextPageID)、总页数($numPages)等等,都可以根据前边这几个东西得到。

以mysql数据库为例,如果要从表内截取某段内容,sql语句可以用:select * from table limit offset, rows。看看下面一组sql语句,尝试一下发现其中的规率。

前10条记录:select * from table limit 0,10

第11至20条记录:select * from table limit 10,10

第21至30条记录:select * from table limit 20,10

……

这一组sql语句其实就是当$PageSize=10的时候取表内每一页数据的sql语句,我们可以总结出这样一个模板:

select * from table limit ($CurrentPageID - 1) * $PageSize, $PageSize

拿这个模板代入对应的值和上边那一组sql语句对照一下看看是不是那么回事。搞定了最重要的如何获取数据的问题以后,剩下的就仅仅是传递参数,构造合适的sql语句然后使用php从数据库内获取数据并显示了。以下我将用具体代码加以说明。

3、简单代码

请详细阅读以下代码,自己调试运行一次,最好把它修改一次,加上自己的功能,比如搜索等等。

<?php

// 建立数据库连接

$link = mysql_connect("localhost", "mysql_user", "mysql_password")

or die("Could not connect: " . mysql_error())

// 获取当前页数

if( isset($_GET['page']) ){

$page = intval( $_GET['page'] )

}

else{

$page = 1

}

// 每页数量

$PageSize = 10

// 获取总数据量

$sql = "select count(*) as amount from table"

$result = mysql_query($sql)

$row = mysql_fetch_row($result)

$amount = $row['amount']

// 记算总共有多少页

if( $amount ){

if( $amount <$page_size ){ $page_count = 1}//如果总数据量小于$PageSize,那么只有一页

if( $amount % $page_size ){ //取总数据量除以每页数的余数

$page_count = (int)($amount / $page_size) + 1 //如果有余数,则页数等于总数据量除以每页数的结果取整再加一

}else{

$page_count = $amount / $page_size //如果没有余数,则页数等于总数据量除以每页数的结果

}

}

else{

$page_count = 0

}

// 翻页链接

$page_string = ''

if( $page == 1 ){

$page_string .= '第一页|上一页|'

}

else{

$page_string .= '<a href="/?page=1>"第一页</a>|<a href="/?page='."($page-1).'>上一页</a>|'

}

if( ($page == $page_count) || ($page_count == 0) ){

$page_string .= '下一页|尾页'

}

else{

$page_string .= '<a href="/?page='."($page+1).'>下一页</a>|<a href="/?page='."$page_count.'>尾页</a>'

}

// 获取数据,以二维数组格式返回结果

if( $amount ){

$sql = "select * from table order by id desc limit ". ($page-1)*$page_size .", $page_size"

$result = mysql_query($sql)

while ( $row = mysql_fetch_row($result) ){

$rowset[] = $row

}

}else{

$rowset = array()

}

// 没有包含显示结果的代码,那不在讨论范围,只要用foreach就可以很简单的用得到的二维数组来显示结果

?>

4、OO风格代码

以下代码中的数据库连接是使用的pear db类进行处理

<?php

// FileName: Pager.class.php

// 分页类,这个类仅仅用于处理数据结构,不负责处理显示的工作

Class Pager

{

var $PageSize //每页的数量

var $CurrentPageID //当前的页数

var $NextPageID //下一页

var $PreviousPageID //上一页

var $numPages //总页数

var $numItems //总记录数

var $isFirstPage//是否第一页

var $isLastPage //是否最后一页

var $sql//sql查询语句

function Pager($option)

{

global $db

$this->_setOptions($option)

// 总条数

if ( !isset($this->numItems) )

{

$res = $db->query($this->sql)

$this->numItems = $res->numRows()

}

// 总页数

if ( $this->numItems >0 )

{

if ( $this->numItems <$this->PageSize ){ $this->numPages = 1}

if ( $this->numItems % $this->PageSize )

{

$this->numPages= (int)($this->numItems / $this->PageSize) + 1

}

else

{

$this->numPages = $this->numItems / $this->PageSize

}

}

else

{

$this->numPages = 0

}

switch ( $this->CurrentPageID )

{

case $this->numPages == 1:

$this->isFirstPage = true

$this->isLastPage = true

break

case 1:

$this->isFirstPage = true

$this->isLastPage = false

break

case $this->numPages:

$this->isFirstPage = false

$this->isLastPage = true

break

default:

$this->isFirstPage = false

$this->isLastPage = false

}

if ( $this->numPages >1 )

{

if ( !$this->isLastPage ) { $this->NextPageID = $this->CurrentPageID + 1}

if ( !$this->isFirstPage ) { $this->PreviousPageID = $this->CurrentPageID - 1}

}

return true

}

/***

*

* 返回结果集的数据库连接

* 在结果集比较大的时候可以直接使用这个方法获得数据库连接,然后在类之外遍历,这样开销较小

* 如果结果集不是很大,可以直接使用getPageData的方式获取二维数组格式的结果

* getPageData方法也是调用本方法来获取结果的

*

***/

function getDataLink()

{

if ( $this->numItems )

{

global $db

$PageID = $this->CurrentPageID

$from = ($PageID - 1)*$this->PageSize

$count = $this->PageSize

$link = $db->limitQuery($this->sql, $from, $count) //使用Pear DB::limitQuery方法保证数据库兼容性

return $link

}

else

{

return false

}

}

/***

*

* 以二维数组的格式返回结果集

*

***/

function getPageData()

{

if ( $this->numItems )

{

if ( $res = $this->getDataLink() )

{

if ( $res->numRows() )

{

while ( $row = $res->fetchRow() )

{

$result[] = $row

}

}

else

{

$result = array()

}

return $result

}

else

{

return false

}

}

else

{

return false

}

}

function _setOptions($option)

{

$allow_options = array(

'PageSize',

'CurrentPageID',

'sql',

'numItems'

)

foreach ( $option as $key =>$value )

{

if ( in_array($key, $allow_options) &&($value != null) )

{

$this->$key = $value

}

}

return true

}

}

?>

<?php

// FileName: test_pager.php

// 这是一段简单的示例代码,前边省略了使用pear db类建立数据库连接的代码

require "Pager.class.php"

if ( isset($_GET['page']) )

{

$page = (int)$_GET['page']

}

else

{

$page = 1

}

$sql = "select * from table order by id"

$pager_option = array(

"sql" =>$sql,

"PageSize" =>10,

"CurrentPageID" =>$page

)

if ( isset($_GET['numItems']) )

{

$pager_option['numItems'] = (int)$_GET['numItems']

}

$pager = @new Pager($pager_option)

$data = $pager->getPageData()

if ( $pager->isFirstPage )

{

$turnover = "首页|上一页|"

}

else

{

$turnover = "<a href='?page=1&numItems=".$pager->numItems."'>首页</a>|<a href="/?page=".$pager->PreviousPageID."&numItems=".$pager->numItems."'>上一页</a>|"

}

if ( $pager->isLastPage )

{

$turnover .= "下一页|尾页"

}

else

{

$turnover .= "<a href="/?page=".$pager->NextPageID."&numItems=".$pager->numItems."'>下一页</a>|<a href="/?page=".$pager->numPages."&numItems=".$pager->numItems."'>尾页</a>"

}

?>

需要说明的地方有两个:

这个类仅仅处理数据,并不负责处理显示,因为我觉得将数据的处理和结果的显示都放到一个类里边实在是有些勉强。显示的时候情况和要求多变,不如自己根据类给出的结果处理,更好的方法是根据这个Pager类继承一个自己的子类来显示不同的分页,比如显示用户分页列表可以:

<?php

Class MemberPager extends Pager

{

function showMemberList()

{

global $db

$data = $this->getPageData()

// 显示结果的代码

// ......

}

}

/// 调用

if ( isset($_GET['page']) )

{

$page = (int)$_GET['page']

}

else

{

$page = 1

}

$sql = "select * from members order by id"

$pager_option = array(

"sql" =>$sql,

"PageSize" =>10,

"CurrentPageID" =>$page

)

if ( isset($_GET['numItems']) )

{

$pager_option['numItems'] = (int)$_GET['numItems']

}

$pager = @new MemberPager($pager_option)

$pager->showMemberList()

?>

第二个需要说明的地方就是不同数据库的兼容性,在不同的数据库里截获一段结果的写法是不一样的。

mysql: select * from table limit offset, rows

pgsql: select * from table limit m offset n

......

所以要在类里边获取结果的时候需要使用pear db类的limitQuery方法。

ok,写完收功,希望花时间看完这些文字的你不觉得是浪费了时间。

pageNow代表当前页面,第一页。

第一部分:看一下分页的基本原理:

对上面的mysql语句说明:limit 10000,20的意思扫描满足条件的10020行,扔掉前面的10000行,返回最后的20行,问题就在这里,如果是limit 100000,100,需要扫描100100行,在一个高并发的应用里,每次查询需要扫描超过10W行,性能肯定大打折扣。文中还提到limit n性能是没问题的,因为只扫描n行。

第二部分:根据雅虎的几位工程师带来了一篇Efficient Pagination Using MySQL的报告内容扩展:在文中提到一种clue的做法,给翻页提供一些线索,比如还是SELECT * FROM message ORDER BY id DESC,按id降序分页,每页20条,当前是第10页,当前页条目id最大的是1020,最小的是1000,如果我们只提供上一页、下一页这样的跳转(不提供到第N页的跳转),那么在处理上一页的时候SQL语句可以是:

处理下一页的时候SQL语句可以是:

不管翻多少页,每次查询只扫描20行。

缺点是只能提供上一页、下一页的链接形式,但是我们的产品经理非常喜欢“上一页 1 2 3 4 5 6 7 8 9 下一页”这样的链接方式,怎么办呢?

如果LIMIT m,n不可避免的话,要优化效率,只有尽可能的让m小一下,我们扩展前面的clue做法,还是SELECT * FROM message ORDER BY id DESC,按id降序分页,每页20条,当前是第10页,当前页条目id最大的是2519,最小的是2500

当是第10页的SQL如下:

比如要跳到第9页,SQL语句可以这样写:

比如要跳到第8页,SQL语句可以这样写:

原理还是一样,记录住当前页id的最大值和最小值,计算跳转页面和当前页相对偏移,由于页面相近,这个偏移量不会很大,这样的话m值相对较小,大大减少扫描的行数。其实传统的limit m,n,相对的偏移一直是第一页,这样的话越翻到后面,效率越差,而上面给出的方法就没有这样的问题。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/sjk/9992063.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-04
下一篇 2023-05-04

发表评论

登录后才能评论

评论列表(0条)

保存