Last active
February 15, 2018 11:20
-
-
Save thclark/e9bb969e402cc861df3cdb624f9e7759 to your computer and use it in GitHub Desktop.
An example of the N+1 database insertion problem in django
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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': '[email protected]', 'secondary_email': '[email protected]'}, | |
{'username': 'anniebloggs', 'primary_email': '[email protected]', 'secondary_email': '[email protected]'}]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment