" name="sm-site-verification"/>
侧边栏壁纸
博主头像
PySuper博主等级

千里之行,始于足下

  • 累计撰写 203 篇文章
  • 累计创建 14 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录
Web

Django ORM查询优化(MySQL)

PySuper
2020-11-15 / 0 评论 / 0 点赞 / 24 阅读 / 9030 字
温馨提示:
所有牛逼的人都有一段苦逼的岁月。 但是你只要像SB一样去坚持,终将牛逼!!! ✊✊✊

MySQL索引

MySQL 数据库 高级

索引

  • 索引的一个主要目的就是加快检索表中数据
  • 是经过某种算法优化过的,因而查找次数要少的多
  • 由此可见,索引是用来定位的

分类

  • 普通索引
  • 唯一索引
  • 全文索引
  • 联合索引

QuerySet

可切片

  • 使用Python 的切片语法来限制查询集记录的数目
  • 它等同于SQL 的LIMIT 和OFFSET 子句。
Entry.objects.all()[:5]      # (LIMIT 5)
Entry.objects.all()[5:10]    # (OFFSET 5 LIMIT 5)
  • 不支持负的索引(例如Entry.objects.all()[-1])
  • 通常,查询集 的切片返回一个新的查询集 —— 它不会执行查询。

###可迭代

articleList=models.Article.objects.all()

for article in articleList:
    print(article.title)

惰性查询

  • 查询集 是惰性执行的 —— 创建查询集不会带来任何数据库的访问
  • 你可以将过滤器保持一整天,直到查询集 需要求值时,Django 才会真正运行这个查询。
queryResult=models.Article.objects.all() # not hits database
 
print(queryResult) # hits database
 
for article in queryResult:
    print(article.title)    # hits database
  • 一般来说,只有在“请求”查询集 的结果时才会到数据库中去获取它们。
  • 当你确实需要结果时,查询集 通过访问数据库来求值。

缓存机制

  • 每个查询集都包含一个缓存来最小化对数据库的访问
  • 理解:
    • 在一个新创建的查询集中,缓存为空。
    • 首次对查询集进行求值, 同时查询数据库,Django 将保存查询的结果到查询集的缓存中并返回明确请求的结果
      • 例如,如果正在迭代查询集,则返回下一个结果
    • 接下来对该查询集 的求值将重用缓存的结果。
print([a.title for a in models.Article.objects.all()])
print([a.create_time for a in models.Article.objects.all()])

# 这意味着相同的数据库查询将执行两次,显然倍增了你的数据库负载
# 同时,还有可能两个结果列表并不包含相同的数据库记录,因为在两次请求期间有可能有Article被添加进来或删除掉
# 为了避免这个问题,只需保存查询集并重新使用它:

queryResult=models.Article.objects.all()
print([a.title for a in queryResult])
print([a.create_time for a in queryResult])

Model建立索引

class Book(models.Model)
    name = models.CharField(max_length=64)

    class Meta:
        # 联合索引: 索引的一个主要目的就是加快检索表中数据
        index_together = ('tag1', 'tag2')

        # 联合唯一索引:两个字段全部重复才算重复
        unique_together = ('tag3', 'tag4')

        # 排序字段
        ordering = 'ordering_tag'

        # 自定义表名(/admin/中显示的表名称)
        verbose_name = db_table = 'table_book'

ORM查询优化

减少连接

entry.blog.id
# 优于
entry.blog__id


# 善于使用批量插入记录,如:
Entry.objects.bulk_create([
    Entry(headline="Python 3.0 Released"),
    Entry(headline="Python 3.1 Planned")
])
# 优于
Entry.objects.create(headline="Python 3.0 Released")
Entry.objects.create(headline="Python 3.1 Planned")
# 前者只连接一次数据库,而后者连接两次


# 还有相似的动作需要注意的,如:多对多的关系,
my_band.members.add(me, my_friend)
# 优于
my_band.members.add(me)
my_band.members.add(my_friend)

only()

只查某些字段
可以通过链式调用,不过等于又重新执行了查询该字段的sql,相当于在重新查了一遍

result= Book.objects.all().only("name","price")   # 只查了 "name","price" 两个字段
Book.objects.all()    # 书的所有字段都查了一遍
print(result.first().gender)    # 获取其他字段

defer()

除了某些字段,其他的都查

Book.objects.all().defer("name","price")  # 除了 "name","price" 两个字段,其他的字段都查

values()

后续不能再点出其他字段了

Book.objects.values("name","price")   # 只查了 "name","price" 两个字段

自定义查询

from django.db.models import Aggregate, CharField

# 自定义聚合函数的名字
class Concat(Aggregate):  # 写一个类继承Aggregate,
    function = 'GROUP_CONCAT'
    template = '%(function)s(%(distinct)s%(expressions)s)'

    def __init__(self, expression, distinct=False, **extra):
        super(Concat, self).__init__(
            expression,
            distinct='DISTINCT ' if distinct else '',
            output_field=CharField(),
            **extra)

# eg: Book.objects.aggregate( name=Concat("name") )

总结

  • 1、数据库技术进行优化,包括给字段加索引,设置唯一性约束等等;
  • 2、查询过滤工作在数据库语句中做,不要放在代码中完成(看情况);
  • 3、如果要一次查询出集合的数量,使用count函数,而不是len函数,但是如果后面还需要到集合,那就用len,因为count还需要进行一次数据库的操作;
  • 4、避免过多的使用count和exists函数;
  • 5、如果需要查询对象的外键,则使用外键字段而不是使用关联的外键的对象的主键;
a.b_id # 正确
a.b.id # 错误
  • 6、在通过all语句查询时,不要做跨表查询,只查询当前表中有的数据,否则查询语句的性能会下降很多;
    比如:a表存在外键b表: a.b.all() # 错误
  • 7、如果想要查询其他表的数据,则加上select_related(ForeignKey字段名,其实就是主动联表查询,性能也会下降),如果有多个,则在括号中加上;
  • 8、加only参数是从查询结果中只取某个字段,而另外一个defer方法则是从查询结果中排除某个字段;
  • 9、不要获取你不需要的东西,可以通过values和value_list实现;
    values返回的是字典数组,比如:[{‘key1’: value1, ‘key2’: value2}, {‘key1’: value3, ‘key2’: value4}]
    value_list返回的是tuple数组 [(‘value1’, ‘value2’), (‘value3’, ‘value4’)]
    value_list+flat=True返回的是数组 [‘value1’, …]
  • 10、如果想知道是否存在至少一个结果,使用exists,而不是使用if QuerySet;但是如果后面需要用到前面的QuerySet,那就可以使用if 判断;
# Don't waste a query if you are using the queryset
books = Book.objects.filter(..)
if len(books) > 5:
    do_stuff_with_books(books)
    
# If you aren't using the queryset use count
books = Book.objects.filter(..)
if books.count() > 5:
    do_some_stuff()
    
# But never
if len(Book.objects.filter(..)) > 5:
    do_some_stuff()
  • 11、在任何位置使用QuerySet.exists()或者QuerySet.count()都会导致额外的查询;
  • 12、不要做无所谓的排序,排序并非没有代价,每个排序的字段都是数据库必须执行的操作;
  • 13、如果要插入多条数据,则使用bulk_create来批量插入,减少sql查询的数量;
  • 14、对于缓存的QuerySet对象使用with标签,可以让数据被缓存起来使用;
  • 15、使用QuerySet.extra明确的指出要查询的字段;
  • 16、批量的更新和删除则使用Queryset.update和delete函数,但是更新操作注意对象的缓存;
  • 17、使用QuerySet.Iterator迭代大数据;
    当你获得一个queryset的时候,django会缓存下来,保存在内存中
    如果需要对queryset进行多次的循环,那么这种缓存无可厚非;
    但是如果你只需要进行一次的循环,那么其实并不需要缓存,这个使用就可以使用iterator;
for book in Books.objects.all().iterator():
    do_stuff(book)
  • 18、如果想判断是否存在外键,只需要判断外键的id即可;
  • 19、不要在循环中查询,而是提前取出,并且做好映射关系,这样在循环中直接通过字典的形式获取到;
  • 20、当计算出一个QuerySet的时候,如果还需要进行多次循环的话,则可以先保留着这个缓存,但是如果只是使用一次的话,没有必要使用到缓存;
  • 21、python优化:排序尽量使用 .sort(), 其中使用 key 比 cmp 效率更高
  • 利用queryset lazy的特性去优化代码,尽可能的减少连接数据库的次数.
  • 如果查出的 queryset 只用一次,可以使用 iterator () 去来防止占用太多的内存
    • e.g.for star in star_set.iterator(): print(star.name).
    • 感兴趣可以看看 ModelIterable 中重写的 iter 方法.
  • 尽可能把一些数据库层级的工作放到数据库
  • 一次性拿出所有你要的数据,不去取那些你不需要的数据.
    • 巧用 select_related (), prefetch_related () 和 values_list (), values (),
    • 如果只需要 id 字段的话,用 values_list (‘id’, flat=True) 也能节约很多资源
    • 或者使用 defer() 和 only() 方法:不加载某个字段 (用到这个方法就要反思表设计的问题了) / 只加载某些字段.
    • 如果不用 select_related 的话,去取外键的属性就会连数据再去查找.
  • bulk (批量) 地去操作数据,比如 bulk_create
  • 查找一条数据时,尽量用有索引的字段去查询,O (1) 或 O (log n) 和 O (n) 差别还是很大的
  • 用 count() 代替 len(queryset), 用 exists() 代替 if queryset:

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区