zoukankan      html  css  js  c++  java
  • Django模型类查询的惰性求值以及模型分页

    以下是我对django模型类的一些探索,我原本是做php开发的,所以在接触django的模型类的时候,多多少少受到了固有思维的影响。现在把我的探索记录下来,也分享给大家。如有问题,还请多多指正。
    基础代码如下:

    def test(request):
        headArticle = Article.objects.order_by('?')[:2]
    
        return HttpResponse('finished')
    

    同时,我使用mysql的查询日志记录,想知道什么时候,网站访问了数据库。

    一、模型类的使用

    • 模型类只有在使用的时候,才会去查询数据库
      单独的赋值,没有操作的,也没有执行查询。真正当该数据被使用的时候,才是去计算,去求值。这也是惰性求值的特点。
      如下的代码是没有查询操作的。
      def test(request):
          headArticle = Article.objects.order_by('?')[:2]
      
          return HttpResponse('finished')
      
      当我把代码内部,写入一个print操作的时候,此时headArticle才真正被使用到,数据库才真正去查询数据。
      def test(request):
          headArticle = Article.objects.order_by('?')[:2]
          print(headArticle)
          return HttpResponse('finished')
      
    • 同一个模型对象,多次调用,会多次查询。
      def test(request):
          headArticle = Article.objects.order_by('?')[:2]
          print(headArticle)
          print(headArticle)
          return HttpResponse('finished')
      
      像上面这样,打印两次headArticle,会造成网站对数据库查询两次,因为我使用随机结果,所以,从打印的结果,就能看出来,同样的一个变量,两次打印,有不同的结果。
      [15/May/2020 18:35:41] "GET /test HTTP/1.1" 200 8
      <QuerySet [<Article: mysql对数据结果的前置和后置补0以及判断值的长度>, <Article: Laravel-admin的安装和使用>]>
      <QuerySet [<Article: 三分钟了解PHP的进程和线程>, <Article: laravel中处理集合的函数>]>
      
      这也算是python语言的一个特点吧。所以,使用模型类的时候,注意不要滥用,像这种查询尽量就使用一次,避免造成不必要的多次查询。

    二、filter和get的区别

        qs1 = Article.objects.get(pk=1)
        qs2 = Article.objects.filter(id=2)
    

    get 不会等待,直接出结果。
    filter 则会在结果有使用的时候,进行查询。

    此时运行的时候,qs1直接查询了数据库,而qs2并未查询。

    三、union的理解

    这是官方给出的示例。

    >>> qs1 = Author.objects.values_list('name')
    >>> qs2 = Entry.objects.values_list('headline')
    >>> qs1.union(qs2).order_by('name')
    

    当时,我还好奇,都查出结果了,再联合,岂不是消耗性能?现在看来,是自己想多了。

    def test(request):
        qs1 = Article.objects.filter(id=1)
        qs2 = Article.objects.filter(id=2)
        result = qs1.union(qs2)
        
        print(result)
    
        return HttpResponse('finished')
    

    如上代码,qs1qs2并未进行查询操作。即便是result变量做联合的时候,也没有进行数据查询。在print打印result之后,才会做真正的查询操作。
    下面是查询的语句,是一个联合查询语句,sql内容如下:

    (
    	SELECT
    		`blog_article`.`id`,
    		
    		...
    		
    		`blog_article`.`updated_at`
    	FROM
    		`blog_article`
    	WHERE
    		`blog_article`.`id` = 1
    )
    UNION
    	(
    		SELECT
    			`blog_article`.`id`,
    			
    			...
    			
    			`blog_article`.`updated_at`
    		FROM
    			`blog_article`
    		WHERE
    			`blog_article`.`id` = 2
    	)
    LIMIT 21
    

    可以看出,djangounionlaravel还是有区别的。在django中大胆使用union就行了,只要使用合理,就不用担心它查询次数太多。

    四、Django分页

    1. 如果,你看到这里 ,也就明白了,为啥django分页的存在了。下面是对结果以每页5个数量,来进行分页。

      def test(request):
          list = Article.objects.all
          paginator = Paginator(list, 5)
      	print(paginator)
      	
          return HttpResponse('finished')
      

      同样的话,此时的数据库查询,包括分页器操作,都并未执行。
      即便是加入print语句,他的打印结果,也只是对象名,此时并未查询数据库,因为真正的结果集并未使用。
      什么时候查询呢?不急,慢慢来

    2. 当我们获取某一页数据的时候,比如下面的这种情况,会怎样呢?

      def test(request):
          list = Article.objects.all()
          paginator = Paginator(list, 5)
      
          page_obj = paginator.get_page(2)
          print(page_obj)
          
          return HttpResponse('finished')
      

      此时,开始查询数据库了。
      命令行输出的是,分页的信息。可以看到是个yield生成器。

      UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list: <class 'blog.models.Article'> QuerySet.
        paginator = Paginator(list, 5)
      <Page 2 of 44>
      

      但是这里,仅仅执行了查询总数的sql语句。

      SELECT COUNT(*) AS `__count` FROM `blog_article`
      
    3. 当我真正的想读取分页的内容的时候,此时才是真正的数据列表查询开始,像下面的这样

      def test(request):
          list = Article.objects.all()
          paginator = Paginator(list, 5)
      
          page_obj = paginator.get_page(2)
          print(page_obj.object_list)
      
          return HttpResponse('finished')
      

      此时查询的语句如下:

      SELECT `blog_article`.`id`,  ...  `blog_article`.`updated_at` FROM `blog_article`  LIMIT 5 OFFSET 5
      

      可以看到,它已经成功个了对sql语句加入的限制条件。可以看出来,django的分页计算确实高明,能正确的算出你想要的数据,你分页的时候,就可以考虑直接使用了。有关更详细的操作,建议参照django官方文档-分页

      另外:更多关于yield生成器的使用方法,请参考官方文档-生成器,或者关注我后期的对yield的讲解。
      如有需要查看django的分页文档,请参照django-分页

      感想:如果你去网上查django分页,你就会反向,有些文档,对官方给出的Paginator分页嗤之以鼻,还有很多人建议你使用手动分页。我在开始的时候,也相信了他们的言论,做出了错误的选择,使用了手动分页。
      其实,如果,你不了解Paginator分页背后的原理的话,看到Paginator分页的代码,很容易觉得,django就是把所有的数据都是查询出来,然后,在程序上所做的分页。其实并不是,它的分页跟你手动写出来的分页,没啥两样,无非就是比你的简单好用。

    五、错误使用方法

    1. 不建议模板多次使用模型
      <ul>
          <li>
              <a href="{% url 'blog:detail' id=headArticle.0.id %}" title="{{ headArticle.0.title }}">
                  <img src="{% static 'blog/images/h1.jpg' %}" alt="{{ headArticle.0.title }}">
                  <span>{{ headArticle.0.title }}</span>
              </a>
          </li>
          <li>
              <a href="{% url 'blog:detail' id=headArticle.1.id %}" title="{{ headArticle.1.title }}">
                  <img src="{% static 'blog/images/h2.jpg' %}" alt="{{ headArticle.1.title }}">
                  <span>{{ headArticle.1.title }}</span></a>
          </li>
      </ul>
      
      如上所示,通过点语法获取第一个和第二元素的时候,会造成,多次的数据库查询。
      即便是同一个headArticle.0.title,多次使用,也会查询多次。 所以,使用的时候,注意避免这种方式使用 。
      尽量使用循环,或者自己在控制器的时候,循环组合成需要的字典数字。
    2. 不建议手动分页
      这是我的早期写的手动分页代码,代码量还是比较多的。
      class List(Common, Side):
          template_name = 'blog/list.html'
      
          def get_context_data(self, **kwargs):
              context = super().get_context_data(**kwargs)
              # 定义分页的起始页面
              page = int(self.request.GET.get('page', 1))
              pageSize = 10
      
              # 计算分页
              startRow = (page - 1) * pageSize
              endRow = page * pageSize
      
              # 查询当前分类的信息
              context['info'] = Category.objects.get(pk=self.kwargs['cate_id'])
              # 构建分页总数
              total = context['info'].article_set.all().count()
              context['page'] = page
              context['totalPage'] = math.ceil(total / pageSize)
              context['articles'] = context['info'].article_set.order_by('-top', '-created_at', '-id').all()[startRow:endRow]
      
              return context
      
      这是我写的手动分页源码,看过第四点的时候,我们知道,django的官方分页,完全不输于性能。我们完全可以修改成,使用官方的分页代码。这种手动分页,除非你乐意手动写分页,一般情况下,我们建议使用官方的。
  • 相关阅读:
    python 自定义异常
    requests
    python 三目运算
    concurrent.futures
    iteratable iterator generator 初步理解总结
    python2 与 python3 的编码
    协程上下文与Job深入解析
    gradle快速入门、groovy环境搭建
    Kotlin项目实战之手机影音---基类抽取、欢迎界面、抽取startactivityandfinish、主界面布局
    Kotlin项目实战之手机影音---项目介绍、项目启动
  • 原文地址:https://www.cnblogs.com/hxsen/p/12897883.html
Copyright © 2011-2022 走看看