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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM