数据库查询的N+1问题

在工作中有时也会涉及到后端开发,后端开发的性能优化多是查询优化。类似前端 DOM 操作很消耗性能,后端开发数据库查询次数也会相当消耗性能。我们在开发中应该尽量减少 SQL 查询次数,避免 N+1 问题来提升性能。接下来就来介绍数据库查询的 N+1 问题。

# SQL N+1

举一个简单的例子,数据库中有一张 cars 表存储汽车的相关信息,还有一张 wheels 表存储汽车的轮胎信息。

查询汽车的语句是:

SELECT * FROM cars;
1

查询每个汽车的轮子的语句是:

SELECT * FROM wheels WHERE car_id = ${car_id};
1

这里的 N 就是汽车的数量,如果需要知道每个汽车的轮胎信息那就正好需要查询 N+1 次。

# 解决 N+1

如果汽车和轮胎是一对一的关系可以通过使用 join 语句来减少数据库查询次数。把查询降为 1 次。修正之后的查询语句是:

SELECT cars.*, wheels.* FROM cars INNER JOIN wheels ON wheels.car_id = cars.id
1

如果汽车和轮胎是一对多的关系可以通过使用:

SELECT * FROM cars;
SELECT * FROM wheels WHERE wheels.car_id IN (1,2,3)
1
2

一次性查询所有需要的轮胎把查询降为两次。

# 现有框架解决方案

# Django

Django 可以使用select-related (opens new window)prefetch-related (opens new window)来解决这个问题。

select-related 主要用于一对一的关系,而 prefetch-related 主要用于多对多或者多对一的关系。

# Ruby on Rails

使用 includes (opens new window) 来解决 N+1 问题。

# 总结

后端开发需要时刻注意 N+1 问题, 在开发模式下,日志会输出 SQL 查询语句,需要多加注意。也可以使用一些工具来检测这个问题。

# 参考

What is the “N+1 selects problem” in ORM (opens new window)

Active Record Query Interface (opens new window)