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次查询)。
- 然后,对于每个学生,查询他们选修的课程(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])