主要观点:
- 介绍了在关系数据库和 ORM 中常见的性能问题,重点关注“Big
IN
s”这种有大量值的IN
子句查询模式。 - 探讨了该模式的起源、性能不佳的原因,以及在项目中可使用的替代方法。
关键信息和重要细节:
- IN 子句与大量值:技术上称为“parenthesized list of scalar expressions”,如
WHERE author_id IN (1,2,3)
,目的是进行过滤,在 Postgres 执行计划中显示为Filter: (author_id = ANY ('{1,2,3}'::integer[]))
。 - 解析、规划与执行:大量值被视为常量,无相关统计信息,导致查询解析和内存使用增加,规划器易误估基数和行选择性,选择顺序扫描而非索引扫描,造成性能下降。
- 直接创建模式:在 Active Record 中,通过
pluck(:id)
收集 id 列表,再将其作为参数传递给另一个查询,如author_ids = Author.where("created_at >=?", 1.year.ago).pluck(:id)
,然后Book.where(author_id: author_ids)
。 - Active Record ORM 方法创建模式:使用
includes()
或preload()
等 eager loading 方法会产生IN
子句 SQL 查询,如Book.includes(:author).limit(10)
会产生两条查询,第二条有IN
子句。 - 修复 N+1 问题:通过添加
includes(:author)
消除 N+1 查询,但会产生有IN
子句的第二条查询,性能可能受影响。 - 使用 eager_load 进行 eager 加载:
eager_load
产生单个使用LEFT OUTER JOIN
的 SQL 查询,无IN
子句,利用了两表的统计信息,更易让规划器正确估计选择性和基数。 - 替代方法使用 ANY 或 SOME:
ANY
或SOME
比IN
更灵活,如使用 CTE 的WITH author_ids AS (SELECT id FROM authors) SELECT title FROM books WHERE author_id = ANY (SELECT id FROM author_ids)
,可使用ActiveRecordExtended
的any
方法在 Active Record 中生成类似查询。 - 使用 VALUES 子句:如
WITH ids(author_id) AS (VALUES(1),(2),(3)) SELECT title FROM books JOIN ids USING (author_id)
或SELECT title FROM books WHERE author_id IN (SELECT id FROM (VALUES(1),(2),(3)) AS v(id))
,VALUES
子句被视为关系,有助于连接策略选择。 - 使用临时表存储 id:创建临时表
CREATE TEMP TABLE temp_ids (author_id int); INSERT INTO temp_ids(author_id) VALUES (1),(2),(3); CREATE INDEX ON temp_ids(author_id); SELECT title FROM books b JOIN temp_ids t ON t.author_id = b.author_id;
。 - 使用 ANY 和数组值:如
SELECT title FROM books WHERE author_id = ANY (ARRAY[1, 2, 3])
,ANY
形式性能更好,支持预处理语句。 - 测试替代查询结构:建议在生产样数据上测试,在 SQL 层工作,使用
EXPLAIN (ANALYZE, BUFFERS)
收集和分析查询执行计划,将 SQL 转换回 Active Record 源代码。 - 在 pg_stat_statements 中查找 IN 子句查询:通过查询
pg_stat_statements
的query
字段查找包含问题IN
查询的统计信息,但可能有重复或近似重复,需筛选'%IN \(%'
。 - Postgres 17 中的改进:2024 年 Postgres 17 版本改进了处理标量表达式和索引的效率,减少重复扫描,提高执行速度,无需更改 SQL 查询或 ORM 代码。
- Postgres 18 中的改进:2025 年 Postgres 18 版本有相关改进,如将
x IN (VALUES...)
自动转换为 ScalarArrayOpExpr,“Squash query list jumbling”以解决pg_stat_statements
中因参数数量导致的查询条目拆分问题。
结论:
- 了解了“Big
IN
s”模式的问题和替代方法,可将其转换为连接查询,或使用ANY
运算符、VALUES
子句和预处理语句,以优化数据库性能。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。