齐金刚,李 滔,李晋军
(西北工业大学 电子信息学院,陕西 西安 710129)
随着互联网的迅速发展,Web数据库的应用越来越多,用户对访问Web数据库页面的速度也要求越来越高,如何快速高效的对Web数据进行查询分页就成为了每个Web应用程序都要面对的一个问题。Django是一个由Python写成的开放源代码Web应用框架,它鼓励快速开发,并遵循MVT设计的设计模式,在Web应用开发方面具有得天独厚的优势,本文就是研究在Django框架下实现Web数据查询分页的方法,并对实现方法进行测试和分析。
把用户请求的数据一次性全部交付给用户是非常不明智的,因为这不但让用户苦苦等待时间较长,而且浪费宝贵的网络资源[1]。而小块数据便于在浏览器上显示,不需要用户滚屏浏览,避免漏掉一些重要的信息;在网络上传输小块数据,可以减少网络流量,提高网页的响应速度;另外,传输小块数据也可以有效减少服务器负载[2]。所以我们要分批把数据传输给用户,也就是分页技术。
图1 Web数据查询执行过程示意图Fig. 1 diagram of the implementation process of Web data query
如图1所示,在进行Web数据查询时,其执行过程可简化为一个三层的网络结构,三层分别在不同的网络环境中,所以实现Web数据查询分页可以在3个不同层次中进行。
就是Web服务器和数据库服务器将满足用户查询条件的数据一次性全部发送给用户,浏览器在显示给用户的时候进行分页。这种分页只能带来浏览的便利,对查询性能的改善没有帮助,本文中不再讨论。
数据库服务器将满足查询条件的数据全部发送给Web服务器,由Web服务器对数据进行分页处理,然后返回给用户需要显示的某一页数据。该方案可以改善Web服务层到客户层之间的网络环境,但是数据库服务层到Web服务层之间的网络流量并没有减少。在ASP.NET、J2EE等进行动态Web应用开发常用的技术中,实现在Web服务层分页的手段较多,比较常用的主要有以下几种[3-4]:
1)将查询结果缓存在HttpSession或有状态bean中实现分页。这种方法使用比较多,其优点是减少了对数据库的访问次数,对数据库连接以及游标等访问资源占用也较少。但其有两个主要的缺点:一是用户可能看到的是过期数据;二是如果数据量非常大时第一次查询遍历结果集会耗费很长时间,并且缓存的数据也会占用大量内存,效率明显下降。
2)用ResultSet移动游标实现分页。每次翻页都查询一次数据库,在需要分页的地方,直接操作ResultSet对象,移动游标到相应位置。这种方法比较简单方便,但由于游标是放在内存中,它将占用内存。特别在操作大型数据库模型进行分页时,每次都加载整个数据源并缓存ResultSet,非常浪费资源。
3)利用ADO Recordset技术实现分页。ADO(Active Data Object)技术是 Microsoft公司支持的一种主要的数据存取技术,ADO提供了一种数据分页技术,是通过 Recordset对象来实现的。利用ADO的Recordset对象可以简单方便地实现数据分页,可是必须将所有数据全部封装进Recordset对象后才能进行分页,如果数据量很大,比如超过上万条,那么封装数据就是一个相当耗时、耗资源的过程,因此该技术只能适合小数据量的分页显示。
4)利用平台自带的分页控件(如DataGrid控件)。当数据量较小时,利用平台自身的控件分页实现起来比较容易,但当数据库中的数据量很大时,一般的内建分页方法就过于粗糙,它的方便性是以牺牲系统性能为代价的。
数据库每次只返回需要显示的数据记录,按照用户的需要提交给Web服务器和用户一页数据。存储过程分页从数据源头就开始进行分页,减少了三层之间的数据流量从而提高了整个网络的查询性能[5]。目前,实现在数据库服务层分页的方法主要有以下两种:
1)调用储存过程。在数据层编写一个存储过程,在其中根据 Web应用程序提供的一些参数(如页面大小、当前页)来执行查询操作得到符合条件的一页记录,然后将查询结果通过Web服务器最终交付给用户。存储过程实际只有在第一次使用时被数据库引擎编译,编译后的映象和过程就存储在服务器上,不必每次执行时都编译,因此能充分发挥服务器的优越性能,大大提高执行速度。但是有些数据库(如SQLite)是不支持存储过程的,使用的时候需要注意。
2)利用数据库自身提供的分页方法。很多数据库自身就提供了各种函数或变量来控制数据库的分页,如MySQL的limit子句,Web应用程序将这样的SQL语句传递给数据库服务器可以实现查询分页。这种方法的优点是执行效率比较高,缺点是由于不同的数据库提供的关于分页的函数或方法各有不同,很难统一。
以上两种方法相比较,利用数据库自身提供的分页方法需要传递整个SQL语句,而调用存储过程只需要传递存储过程名和相应的参数,很大程度上降低了通信负载,进而提高了应用程序的效率,这种情况在数据量大的时候,体现尤为明显。而且两者的调用方法基本一样,所以在本文中只对调用存储过程这种方法进行深入研究。
在Web服务层进行分页是一种比较常用的方法,其执行过程如图2所示。在Django框架中,我们可以分别利用缓存机制和Session框架实现这一功能。
图2 Web层分页执行过程示意图Fig. 2 diagram of the implementation process of pagination in Web layer
利用Django框架的缓存机制实现Web服务层分页,就是当数据库服务器把所有符合条件的数据发送回Web服务器之后,将这些数据放入缓存中,当用户请求不同页数的数据时直接从缓存中读取,发送到用户浏览器。Django框架有着完善的缓存机制,支持内存缓存、数据库缓存、文件系统缓存、本地缓存和仿缓存(供开发时使用)等多种缓存模式,还支持使用自定义缓存后端,这些缓存方法各自有不同的特点和要求,需要你根据自身系统的特点选择使用其中哪一种,并在Django配置文件中进行相应的设定,确保自身系统运转高效[6]。
利用缓存机制实现分页的主要步骤如下:
1)建立数据模型
本文就以建立一个书籍信息的模型为例,其数据模型包含书籍的名字、作者、出版社、出版时间等信息。在models.py文件中,定义模型如下:
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.CharField(max_length=100)
publisher = models.CharField(max_length=50)
publication_date = models.DateField()
2)编写视图函数
假设用户可以从书名、作者、出版社等3个方面进行检索,在视图函数中首先创建一个搜索函数,先从类字典对象request.POST中读取用户发送的检索条件,一般采用get()方法:
title = request.POST.get('title','')
authors = request.POST.get('authors','')
publisher = request.POST.get('publisher','')
在获取用户提交的检索条件后,先对用户的输入进行判别,看其输入是否全部为空、内容是否合法,否则返回错误提示信息。如果用户的检索条件正确,则使用“Book.objects.filter()”语句对数据库进行检索,如果设置的检索条件过于复杂,可使用“Q”对象进行级联查找。对数据库进行检索后获得一个包含所有符合条件记录的QuerySet,然后利用Django的缓存机制将其放入缓存中,以便在结果显示页面中能够多次读取。假设我们的结果集命名为books,将其放入缓存或读出的方法如下:
3)创建模板文件、进行url配置
在模板文件夹下创建模板文件,用以获取用户输入,显示检索分页的结果或是错误信息,然后在urls.py文件中配置路径信息。
利用Django的Sessions框架实现在Web服务层分页与利用Django缓存机制实现这一功能方法基本相同,其主要区别是一个把查询的结果利用缓存机制存放在缓存中,一个把查询的结果利用Sessions框架存放在request.session字典中。
利用Sessions框架实现分页的步骤与利用缓存实现的步骤相同,只需将查寻结果存入缓存、从缓存读取的语句改为从request.session字典中存取即可。
要使用Django的Sessions框架,首先要检查你工程下的配置文件,确保Sessions功能已经打开。之后,在视图函数中就可以像字典一样使用request.session对象。利用其存取查询结果的方法分别为:
在这里,我们只研究存储过程分页的实现方法。存储过程分页,就是在数据层编写一个存储过程,并将要显示页的页码和每页的尺寸作为存储过程的输入参数,这样每次仅将查询结果的一个子集通过Web服务器交付给用户,减少了三层之间的数据流量从而提高了整个网络的查询性能[5]。其执行过程如图3所示。
图3 存储过程分页执行过程示意图Fig. 3 diagram of the implementation process of pagination in storage procedure
存储过程是存储在数据库服务器上的一组预编译的SQL语句,这些语句通常是一些需要频繁操作的任务,它可以接收参数,返回状态值,并且可以嵌套使用。在特定情况下,特别是使用频率高、结构复杂的SQL语句使用存储过程可以提高数据库应用程序的执行效率。以MySQL数据库为例,我们可以定义一个存储过程,接受表名、查询字段、每页记录数、当前页码、排序条件、WHERE条件等参数,返回符合条件记录总数和查询结果集,定义过程如下:
在数据库定义了存储过程之后,就可以在Django框架中进行调用,调用时需要先获取游标对象,然后使用“execute()”方法调用或使用“callproc()”方法进行调用,然后读取查询结果和输出变量。调用存储过程可以在视图函数中直接进行,也可以通过自定义Manager方法调用。
1)在视图函数中直接调用
以调用上面的存储过程为例,我们取出所有记录的第2页,每页10条记录,数据表名为‘paging_book’:
最后,还要在模型Book中加入语句“objects = BookManager()”,如果在视图函数中导入了Book模型,就可以使用下面的语句对存储过程进行调用,并获得查询结果和记录总数:
books,total_num=Book.objects.book_filter(page,num,condition)
通过自定义Manager方法调用存储过程可以在视图函数中很方便的进行多次调用,而避免每次都要将调用代码重写。
根据调用存储过程得到的查询结果和总记录数,可以计算出总页数、页码索引范围等内容,然后将其和查询结果等内容传送至模板上显示给用户,具体过程这里不再赘述。
对上面几种方法进行测试,分页时每页大小为5条记录,结果总数分别为10~100 000条,利用Cache功能是采用内存缓存机制,具体结果如表1所示 。
表1 测试结果Tab.1 The result of test
从上面的结果可以看出,当网站的数据量较小时,这几种分页方法差别不大,此时由于在Web层分页实现方法比较简单,可以考虑采用在Web层分页的方法。但随着网站数据量的增长,在数据库层分页的优势越发明显,分页效率明显优于在Web层分页。特别是在Django框架中Session的值定义为“longtext”类型,在MySQL中此类型所能存储的最大长度为4294967295(232-1)个字符,当查询的结果集比较大会运行出错,所以当查询数据量比较大时不宜使用Session功能分页。而利用自定义Manager方法和直接调用存储过程两种分页方法的效率基本一样,都可以大幅度提高Web查询分页的效率,但利用自定义Manager方法可简化调用过程,并且方便多次调用。
[1] 李光耀,易虎,李波.基于存储过程分页优化Web数据查询性能[J].微计算机应用,2004,25(4):476-479.LI Guang-yao,YI Hu,LI Bo. Performance-optimizing of querying web data based on paging in stored procedure[J].Microcomputer App lications,2004,25(4):476-479.
[2] 王瑞波. 一种分页查询优化方法的研究与实现[D].北京:北京化工大学, 2009.
[3] 黄栎桥,陆鑫.基于Struts框架的Web数据库分页技术[J].计算机应用,2008,8(z1):288-301.HUANG Li-qiao,LU Xin.Pagination technology of Web-based database using Struts framework[J].Journal of Computer Applications,2008,8(z1):288-301.
[4] 岳国伟,梁永全,陈玉娥.ASP.NET中数据分页技术的研究[J].计算机应用研究,2007,24(9):159-161.YUE Guo-wei,LIANG Yong-quan,CHEN Yu-e.Research of data paging technologies in ASP. NET[J].Applicatio Reseach of Computers,2007,24(9):159-161.
[5] 王博,任涛.Web数据库分页浏览方法性能分析[J].现代电子技术,2006,29(10):68-70.WANG Bo, REN Tao.Performance analysis of web database paging browse methods[J].Modern Electronics Technique,2006,29(10):68-70.
[6] Jeff Forcier,Paul Bissex,Wesley Chun.Django Web开发指南[M].许旭铭,等译. 北京:机械工业出版社, 2009.