在Django 1.4及以上版本中使用bulk_create功能创建的项目中,有没有一种方法可以获取这些项目的主键?
在Django 1.4及以上版本中使用bulk_create功能创建的项目中,有没有一种方法可以获取这些项目的主键?
自Django 1.10开始,它现在受到支持(仅限Postgres),这是一个文档链接。
>>> list_of_objects = Entry.objects.bulk_create([
... Entry(headline="Django 2.0 Released"),
... Entry(headline="Django 2.1 Announced"),
... Entry(headline="Breaking: Django is awesome")
... ])
>>> list_of_objects[0].id
1
从变更日志中:
在Django 1.10版本中进行了更改: 当使用PostgreSQL时,支持通过bulk_create()方法设置对象的主键
Django 1.10中,现已支持使用bulk_create()方法创建对象并在PostgreSQL数据库中设置主键。
bulk_create
方法返回的列表与您提供的列表相同,并且该列表中的本地对象(即该列表的成员)未设置它,就像pyriku在他的答案中展示的那样。 - Yushin Washio>>> categories = Category.objects.bulk_create([
Category(titel="Python", user=user),
Category(titel="Django", user=user),
Category(titel="HTML5", user=user),
])
>>> [x.pk for x in categories]
[None, None, None]
这并不意味着你的分类没有主键(pk),只是查询没有检索到它们(如果键是AutoField
)。如果由于某种原因您想要pk,您将需要经典方式保存对象。
bulk_create
这个限制,以便可靠地检索已创建的ID。至少这是我对这个问题的理解。请问人们都使用哪些技巧来实现这一点? - DanH我能想到两种方法:
a)你可以做...
category_ids = Category.objects.values_list('id', flat=True)
categories = Category.objects.bulk_create([
Category(title="title1", user=user, created_at=now),
Category(title="title2", user=user, created_at=now),
Category(title="title3", user=user, created_at=now),
])
new_categories_ids = Category.objects.exclude(id__in=category_ids).values_list('id', flat=True)
如果查询集非常庞大,这可能会有些昂贵。
b)如果模型有一个created_at
字段,
now = datetime.datetime.now()
categories = Category.objects.bulk_create([
Category(title="title1", user=user, created_at=now),
Category(title="title2", user=user, created_at=now),
Category(title="title3", user=user, created_at=now),
])
new_cats = Category.objects.filter(created_at >= now).values_list('id', flat=True)
这有一个限制,即有一个字段存储对象的创建时间。
date_created
字段了,所以虽然最小的努力是无论如何都要添加一个。我唯一担心的是多个查询可能会同时访问数据库,因此我想在 bulk_create
之前和 created_at
查询之后实现某种锁定机制。 - DanHmax(id)
,我尝试过并遇到了问题。MariaDB文档明确指出不要假设PK除唯一性外还有其他任何属性。 - Patrick其实我的同事已经建议了以下解决方案,现在看来这个方案显得非常明显。添加一个新列名叫做bulk_ref
,你需要为每一行填入一个唯一的值。之后只需预先设置好bulk_ref
并查询表格,你就可以轻松检索到插入的记录了。例如:
cars = [Car(
model="Ford",
color="Blue",
price="5000",
bulk_ref=5,
),Car(
model="Honda",
color="Silver",
price="6000",
bulk_ref=5,
)]
Car.objects.bulk_create(cars)
qs = Car.objects.filter(bulk_ref=5)
我将为您分享在InnoDB
(MySQL)
中处理AUTO_INCREMENT
以及在bulk_create
(Django)
中获取主键的方法。
根据bulk_create文档,如果模型的主键是AutoField,则不会像save()函数一样检索并设置主键属性,除非数据库后端支持它(目前只有PostgreSQL支持)。所以我们需要先找出在Django或MySQL中引起问题的原因,然后再寻找解决方案。
Django中的AUTO FIELD
实际上就是MySQL中的AUTO_INCREMENT
。它用于为新行生成唯一标识(参考文献)
当您想要bulk_create
对象(Django)时,意味着在单个SQL查询中插入多行
。但是如何检索最近自动生成的PK(主键)呢?感谢LAST_INSERT_ID。它返回最近执行的INSERT语句的首个自动生成的值......即使其他客户端生成了自己的AUTO_INCREMENT值,此值也不会受到影响。这种行为确保每个客户端都可以检索其自己的ID,而无需关注其他客户端的活动,也无需锁定或事务。
我鼓励您阅读InnoDB中的AUTO_INCREMENT处理和Django代码django.db.models.query.QuerySet.bulk_create
以了解为什么Django尚未支持MySQL。很有意思。请回来并在评论中发表您的想法。
接下来,我将向您展示示例代码:
from django.db import connections, models, transaction
from django.db.models import AutoField, sql
def dict_fetch_all(cursor):
"""Return all rows from a cursor as a dict"""
columns = [col[0] for col in cursor.description]
return [
dict(zip(columns, row))
for row in cursor.fetchall()
]
class BulkQueryManager(models.Manager):
def bulk_create_return_with_id(self, objs, batch_size=2000):
self._for_write = True
fields = [f for f in self.model._meta.concrete_fields if not isinstance(f, AutoField)]
created_objs = []
with transaction.atomic(using=self.db):
with connections[self.db].cursor() as cursor:
for item in [objs[i:i + batch_size] for i in range(0, len(objs), batch_size)]:
query = sql.InsertQuery(self.model)
query.insert_values(fields, item)
for raw_sql, params in query.get_compiler(using=self.db).as_sql():
cursor.execute(raw_sql, params)
raw = "SELECT * FROM %s WHERE id >= %s ORDER BY id DESC LIMIT %s" % (
self.model._meta.db_table, cursor.lastrowid, cursor.rowcount
)
cursor.execute(raw)
created_objs.extend(dict_fetch_all(cursor))
return created_objs
class BookTab(models.Model):
name = models.CharField(max_length=128)
bulk_query_manager = BulkQueryManager()
class Meta:
db_table = 'book_tab'
def test():
x = [BookTab(name="1"), BookTab(name="2")]
create_books = BookTab.bulk_query_manager.bulk_create_return_with_id(x)
print(create_books) # [{'id': 2, 'name': '2'}, {'id': 1, 'name': '1'}]
INT AUTO_INCREMENT
PK字段,它不起作用,即使在隔离级别为serializable
的事务中也不行,因为MariaDB中的PK计数器没有受到事务锁的保护。UUID
字段,在模型类中生成其值,然后将其用作标识符。当您将一堆模型保存到数据库中时,仍然无法获取它们的实际PK,但这没关系,因为在后续查询中,您可以使用它们的UUID唯一地标识它们。# datatime.py
# my datatime function
def getTimeStamp(needFormat=0, formatMS=True):
if needFormat != 0:
return datetime.datetime.now().strftime(f'%Y-%m-%d %H:%M:%S{r".%f" if formatMS else ""}')
else:
ft = time.time()
return (ft if formatMS else int(ft))
def getTimeStampString():
return str(getTimeStamp()).replace('.', '')
# model
bulk_marker = models.CharField(max_length=32, blank=True, null=True, verbose_name='bulk_marker', help_text='ONLYFOR_bulkCreate')
# views
import .........getTimeStampString
data_list(
Category(title="title1", bulk_marker=getTimeStampString()),
...
)
# bulk_create
Category.objects.bulk_create(data_list)
# Get primary Key id
Category.objects.filter(bulk_marker=bulk_marker).values_list('id', flat=True)
可能最简单的解决方法是手动分配主键。这取决于具体情况,但有时从表中开始使用max(id)+1并为每个对象分配递增的数字就足够了。然而,如果多个客户端同时插入记录,则可能需要一些锁定。
Django文档目前在限制部分声明:
如果模型的主键是AutoField,则不会检索和设置主键属性,就像
save()
一样。
但是,有好消息。已经有几个关于从内存中bulk_create
的票据。上面列出的票据最有可能有一个解决方案,很快将被实现,但是显然不能保证时间或是否会实现。
因此,有两种可能的解决方案,
等待并观察此补丁是否能够进入生产环境。您可以通过测试所述解决方案并让 Django 社区了解您的想法/问题来帮助实现这一点。https://code.djangoproject.com/attachment/ticket/19527/bulk_create_and_create_schema_django_v1.5.1.patch
覆盖/编写自己的批量插入解决方案。