N+1问题

在数据库查询中,N+1 问题是一个常见的性能问题,特别是在使用ORM时。这个问题通常出现在需要从一个表中获取多个记录,并且每个记录都有与之关联的其他表记录时(外键关联)。

N和1的含义: 如果你从主表("主查询")中获取了 N 条记录,然后对每条记录都访问了其关联对象,你实际上会执行 N+1 次数据库查询:一次是主查询,其余 N 次是为每个主记录获取关联对象的查询。

一句话说明:其实就是在ORM中,该用JOIN语句时,没有用

示例

假设我们有两个表:一个是学生表,另一个是课程表。

每个学生可以选修多门课程,因此这是一对多的关系。

  • 学生表

    学生ID 姓名
    1 Alice
    2 Bob
    3 Carol
  • 课程表

    课程ID 课程名 学生ID
    1 数学 1
    2 英语 1
    3 物理 2
    4 化学 3
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, Session

engine = create_engine('sqlite:///school.db')

class Student(Base):
    __tablename__ = 'student'
    student_id = Column(Integer, primary_key=True)
    name = Column(String)
    courses = relationship("Course")

class Course(Base):
    __tablename__ = 'course'
    course_id = Column(Integer, primary_key=True)
    course_name = Column(String)
    student_id = Column(Integer, ForeignKey('student.student_id'))

如果你想获取每个学生及其选修的所有课程,一种直观的方法是:

  1. 首先查询学生表,获取所有学生(1次查询)。
  2. 然后,对于每个学生,查询他们选修的课程(N次查询,N是学生数量)。

这样,总共需要N+1次查询。

# 获取所有学生
session = Session()
students = session.query(Student).all()

# 对于每个学生,获取他们的课程
for student in students:
    print(student.name, [course.course_name for course in student.courses])

对应到sql语句是

SELECT * FROM student;

SELECT * FROM course WHERE student_id = 1;  -- For Alice
SELECT * FROM course WHERE student_id = 2;  -- For Bob
SELECT * FROM course WHERE student_id = 3;  -- For Carol

熟悉sql的一看,这不是应该使用LEFT JOIN吗?

SELECT student.student_id, student.name, course.course_name
FROM student
LEFT JOIN course ON student.student_id = course.student_id;

其实在SQLAlchemy中,也有类似的语句,可以使用joinedload来进行预加载,从而一次性获取所有需要的信息

from sqlalchemy.orm import joinedload

# 使用 joinedload 进行预加载,一次查询即可获取所有需要的数据
students = session.query(student).options(joinedload('courses')).all()

for student in students:
    print(student.name, [course.course_name for course in student.courses])
posted @ 2020-06-04 11:09  Jeff_blog  阅读(2083)  评论(0编辑  收藏  举报