Skip to content

Instantly share code, notes, and snippets.

@thclark
Last active February 15, 2018 11:20
Show Gist options
  • Save thclark/e9bb969e402cc861df3cdb624f9e7759 to your computer and use it in GitHub Desktop.
Save thclark/e9bb969e402cc861df3cdb624f9e7759 to your computer and use it in GitHub Desktop.
An example of the N+1 database insertion problem in django
from django.db.models import CharField, Model, OneToOneField, AutoField, EmailField
class User(Model):
id = AutoField(primary_key=True) # The default Id field from django
username = CharField(max_length=30)
profile = OneToOneField(Profile, primary_key=True, related_name='user')
class Profile(Model):
id = AutoField(primary_key=True) # The default Id field from django
primary_email = EmailField()
secondary_email = EmailField()
def bulk_insert(list_of_user_details):
""" Demonstrates the N+1 problem using a bulk creation of users example
Questions to think about:
- Why does the first code block create a ValueError?
- We can use bulk_create on the User model, but not the Profile model or both at once. Why not?
- What happens when instead of 2 users, the bulk_insert is done for 10 users? 20,000 users? 10m users?
- What happens when there's an error halfway through the insertion process?
"""
# First of all, why does the following produce a ValueError like:
# Traceback (most recent call last):
# ...
# ValueError: save() prohibited to prevent data loss due to unsaved related object 'profile' ?
try:
# Why does this produce a value error?
user_details = list_of_user_details[0]
user = User(username=user_details['username'])
profile = Profile(primary_email=user_details['primary_email'], secondary_email=user_details['secondary_email'])
user.profile = profile
user.save()
except ValueError:
pass
# Django has bulk creation for models... Let's just add them all at once! But why won't the following work?
try:
users = []
for user_details in list_of_user_details:
user = User(username=user_details['username'])
user.profile = Profile(primary_email=user_details['primary_email'], secondary_email=user_details['secondary_email'])
users.append(user)
User.objects.bulk_create(users)
except ValueError:
pass
# The following works! But what's the problem with it?
try:
users = []
for user_details in list_of_user_details:
user = User(username=user_details['username'])
profile = Profile(primary_email=user_details['primary_email'], secondary_email=user_details['secondary_email'])
profile.save()
user.profile = profile
users.append(user)
User.objects.bulk_create(users)
except ValueError:
pass
# How can we get over the problem in the previous code block?
bulk_insert([{'username': 'joebloggs', 'primary_email': 'joe@me.com', 'secondary_email': 'jb@gmail.com'},
{'username': 'anniebloggs', 'primary_email': 'annie@me.com', 'secondary_email': 'ab@gmail.com'}])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment