Oracle|ORM框架 n+1问题 导致数据库负载飙高

这个问题排查过程和解决方法都简单,但作为一个框架的问题,可能以后还会遇到,还是记录一下。

一、 问题描述 收到告警数据库cpu load超过10,登上去一看load到了50多(最高108)。检查数据库发现有100多个会话在执行sql,并且执行时间基本都超过10秒,DB中出现大量direct path write/read temp等待事件。
100多个会话的sql均类似,其中YYY是一个复杂的视图,id是视图中多个字段和函数拼出来的字段,无法直接加索引。

select xx from YYY where id in (1); select xx from YYY where id in (2); ... select xx from YYY where id in (100);

经过后续观察这100多个会话总是在同一时间出现,联系业务方这是个很小的功能,并没有同时100多个并发的情况,但有可能用户查询时会选100多个id。由此猜测代码里有一个这样的拆分——把用户输入的100多个id拆成100多个会话来执行,而不是放在一个in语句里执行。

二、 解决方法 对于这个sql,单次执行较快(不到1s),但当并发高时,由于视图复杂存在大量hash join和排序,id字段也无法直接加索引,所以最简单有效的方法是改实现逻辑,直接合并为一个 select xx from YYY where id in (1,2,...,100)语句,只对各表进行一次全表扫描。

三、 n+1问题 在Rails ActiveRecord中,常会嵌套遍历很多orm模型。如:常用的一对多(用户和文章)模型中
class User has_many :articles endclass Article belongs_to :user end

如果有十个用户,那么查询每个用户对应的文章需要查询11次。
User.all.map{|user| user.articles}-- 生成sql为 select * from users select * from articles where user_id = 1 ... select * from articles where user_id = 10

为了解决这个问题,一般的解决方案是:
1. 预加载
使用includes, preload, eager_load
User.includes(:articles).map{|user| user.acticles } --只需要2条查询语句 select * from users select * from articles where user_id in (1..10)


Rails 提供了4种方式来加载关联表的数据:
  • preload:使用一条附加的查询语句来加载关联数据,总是会生成两个sql语句。是默认的User.includes(:articles)加载方式,加入查询条件时使用preload会报错:
User.preload(:articles).where("articles.content = '123' ") #报错 ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: articles.content: SELECT"users".* FROM "users" WHERE (articles.content = '123' ) LIMIT ?

  • includes:加入查询条件不会报错
User.includes(:articles).where("articles.content = '123' ")

而preload的条件中加入users表的查询条件则没有问题:
User.preload(:articles).where("users.name = '123' ")

  • eager_load:使用left outer join进行单次查询,并加载所有关联数据。
  • joins:joins使用inner join来加载关联数据

2.使用Goldiloader
安装 gem 'goldiloader'。默认情况下,所有关联在首次访问时都会自动加载。
可以手动添加代码auto_include,禁止自动预先加载:User.auto_include(false)
classUser < ActiveRecord::Base has_many :articles, ->{ auto_include(false) } end

fully_load选项可用于强制ActiveRecord完全加载关联(并执行任何必要的自动切换加载)
classBlog



参考
https://github.com/salsify/goldiloader
【Oracle|ORM框架 n+1问题 导致数据库负载飙高】https://www.jianshu.com/p/b936a5e5b03e

    推荐阅读