这个问题排查过程和解决方法都简单,但作为一个框架的问题,可能以后还会遇到,还是记录一下。
一、 问题描述 收到告警数据库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