• Django插入多条数据—bulk_create


    在Django中需要向数据库中插入多条数据(list)。使用如下方法,每次save()的时候都会访问一次数据库。导致性能问题:

    for i in resultlist:
        p = Account(name=i) 
        p.save()
    

    在django1.4以后加入了新的特性。使用django.db.models.query.QuerySet.bulk_create()批量创建对象,减少SQL查询次数。改进如下:

    querysetlist=[]
    for i in resultlist:
        querysetlist.append(Account(name=i))        
    Account.objects.bulk_create(querysetlist)
    

    Model.objects.bulk_create() 更快更方便

    常规用法:

    #coding:utf-8
     
    import os
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
     
    '''
    Django 版本大于等于1.7的时候,需要加上下面两句
    import django
    django.setup()
    否则会抛出错误 django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet.
    '''
     
    import django
    if django.VERSION >= (1, 7):#自动判断版本
        django.setup()
     
     
    def main():
        from blog.models import Blog
        f = open('oldblog.txt')
        for line in f:
            title,content = line.split('****')
            Blog.objects.create(title=title,content=content)
        f.close()
     
    if __name__ == "__main__":
        main()
        print('Done!')
    

    使用批量导入:

    import os
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
     
    def main():
        from blog.models import Blog
        f = open('oldblog.txt')
        BlogList = []
        for line in f:
            title,content = line.split('****')
            blog = Blog(title=title,content=content)
            BlogList.append(blog)
        f.close()
         
        Blog.objects.bulk_create(BlogList)
     
    if __name__ == "__main__":
        main()
        print('Done!')
    

    由于Blog.objects.create()每保存一条就执行一次SQL,而bulk_create()是执行一条SQL存入多条数据,做会快很多!当然用列表解析代替 for 循环会更快!!

    #!/usr/bin/env python
    import os
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
     
    def main():
        from blog.models import Blog
        f = open('oldblog.txt')
         
        BlogList = []
        for line in f:
            parts = line.split('****')
            BlogList.append(Blog(title=parts[0], content=parts[1]))
         
        f.close()
             
        # 以上四行 也可以用 列表解析 写成下面这样
        # BlogList = [Blog(title=line.split('****')[0], content=line.split('****')[1]) for line in f]
         
        Blog.objects.bulk_create(BlogList)
     
    if __name__ == "__main__":
        main()
        print('Done!')
    

    例如:

    # 获取数量
    nums = request.POST.get('nums').strip()
    if nums.isdigit() and int(nums) > 0:
        # 方法一
        # for i in range(int(nums)):
        #     device = Device(
        #         category=category,
        #         seat=seat_obj,
        #         asset_code='',
        #         asset_num='V{}-{}'.format(category.name, str(i).zfill(4)),  # V类型-0001编号
        #         use_info='',
        #         operator=operator,
        #         op_type=1
        #     )
        #     device.save()  # 每次save()的时候都会访问一次数据库。导致性能问题
    
        # 方法二
        device_obj_list = []
        for i in range(int(nums)):
            device_obj_list.append(
                Device(
                    category=category,
                    seat=seat_obj,
                    asset_code='---',
                    asset_num='{}-xxxx'.format(category.name),  # 类型-xxxx
                    use_info='---',
                    operator=operator,
                    op_type=1
                )
            )
        Device.objects.bulk_create(device_obj_list)  # 使用django.db.models.query.QuerySet.bulk_create()批量创建对象,减少SQL查询次数
        messages.info(request, '批量添加{}条数据完成!'.format(nums))
    

    批量导入时数据重复的解决方法

    如果你导入数据过多,导入时出错了,或者你手动停止了,导入了一部分,还有一部分没有导入。或者你再次运行上面的命令,你会发现数据重复了,怎么办呢?

    django.db.models 中还有一个函数叫 get_or_create(),之前文章中也提到过,有就获取过来,没有就创建,用它可以避免重复,但是速度可以会慢些,因为要先尝试获取,看看有没有

    只要把上面的:

    Blog.objects.create(title=title,content=content)
    

    换成下面的就不会重复导入数据了

    Blog.objects.get_or_create(title=title,content=content)
    

    返回值是(BlogObject, True/False)新建时返回 True, 已经存在时返回 False。

    事务探究

    bulk_create来批量插入,可是使用了这个方法还需要在自己添加一个事务吗? 还是django本身对这个方法进行了事务的封装?

    查看了源码(django1.5):在 django/db/models/query.py 中,看到这样的片段

    with transaction.commit_on_success_unless_managed(using=self.db):
                if (connection.features.can_combine_inserts_with_and_without_auto_increment_pk
                    and self.model._meta.has_auto_field):
                    self._batched_insert(objs, fields, batch_size)
                else:
                    objs_with_pk, objs_without_pk = partition(lambda o: o.pk is None, objs)
                    if objs_with_pk:
                        self._batched_insert(objs_with_pk, fields, batch_size)
                    if objs_without_pk:
                        fields= [f for f in fields if not isinstance(f, AutoField)]
                        self._batched_insert(objs_without_pk, fields, batch_size)
    

    这里我们看到了一个transaction的调用 transaction.commit_on_success_unless_managed(using=self.db),那么这句话是什么意思呢?

    看看他的定义: django/db/transaction.py中

    def commit_on_success_unless_managed(using=None, savepoint=False):
        """
        Transitory API to preserve backwards-compatibility while refactoring.
     
        Once the legacy transaction management is fully deprecated, this should
        simply be replaced by atomic. Until then, it's necessary to guarantee that
        a commit occurs on exit, which atomic doesn't do when it's nested.
     
        Unlike atomic, savepoint defaults to False because that's closer to the
        legacy behavior.
        """
        connection = get_connection(using)
        if connection.get_autocommit() or connection.in_atomic_block:
            return atomic(using, savepoint)
        else:
            def entering(using):
                pass
     
            def exiting(exc_type, using):
                set_dirty(using=using)
     
            return _transaction_func(entering, exiting, using)
    

    没怎么看懂这个方法的解释,从代码结构来看应该是有事务的。

    那自己做个试验把,往数据库批量插入2条数据,一个正确的,一个错误的看看结果如何?

    ipython做了个试验

    from mngm.models import Area
    a1=Area(areaname="China", code="CN", parentid='1', level='3')
    a2=Area(id=1, areaname="China", code="CN", parentid='1', level='3')  #错误的记录
    Area.objects.bulk_create([a1, a2])
    IntegrityError: (1062, "Duplicate entry '1' for key 'PRIMARY'")
     
    a2=Area(areaname="Chinaa", code="CN", parentid='1', level='3')        #正确的记录
    Area.objects.bulk_create([a1, a2])
    [<Area: Area object>, <Area: Area object>]
    

    所以这个操作框架已经实现了事务处理,不需要自己再添加事务就好了。

    你可能正好不需要这种事务处理,看看

    <https://rodmtech.net/docs/django/django-bulk_create-without-integrityerror-rollback/

  • 相关阅读:
    使用ActivityGroup来切换Activity和Layout
    Fragment
    [Java]LeetCode297. 二叉树的序列化与反序列化 | Serialize and Deserialize Binary Tree
    [Swift]LeetCode298. 二叉树最长连续序列 $ Binary Tree Longest Consecutive Sequence
    [Swift]LeetCode296. 最佳开会地点 $ Best Meeting Point
    [Swift]LeetCode294. 翻转游戏之 II $ Flip Game II
    [Swift]LeetCode293. 翻转游戏 $ Flip Game
    [Swift]LeetCode291. 单词模式 II $ Word Pattern II
    [Postman]发出SOAP请求(18)
    [Postman]生成代码段(17)
  • 原文地址:https://www.cnblogs.com/jiumo/p/12240831.html
Copyright © 2020-2023  润新知