我将分享AUTO_INCREMENTInnoDB(MySQL) 中的处理以及bulk_create(Django) 时获取主键的方法
根据bulk_create docIf the model’s primary key is an AutoField it does not retrieve and set the primary key attribute, as save() does, unless the database backend supports it (currently PostgreSQL).所以我们需要在Django或MySQL中找出问题的原因,然后再寻找解决方案。
Django 中的AUTO FIELD 实际上是MySQL 中的AUTO_INCREMENT。它用于为新行生成唯一标识 (ref)
你想要bulk_create 对象(Django)意味着insert multiple rows in a single SQL query。但是如何检索最近自动生成的 PK(主键)?感谢LAST_INSERT_ID。 It returns first value automatically generated of the most recently executed INSERT statement...This value cannot be affected by other clients, even if they generate AUTO_INCREMENT values of their own. This behavior ensures that each client can retrieve its own ID without concern for the activity of other clients, and without the need for locks or transactions.
我鼓励您阅读 AUTO_INCREMENT Handling in InnoDB 和 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'}]
想法是使用cursor 执行raw insert sql 然后取回created_records。根据AUTO_INCREMENT handling in InnoDB,它确保不会有任何记录从PKcursor.lastrowid - len(objs) + 1 to cursor.lastrowid(cursor.lastrowid)打断你的objs。
奖励:它正在我的公司进行生产。但是你需要关心size affect,这就是为什么 Django 不支持它。