SendLog.all.each do | log |
# do something……
end
Model.all.each 会使 Active Record 一次性取回整个数据表,为每条记录创建模型对象,并把整个模型对象数组保存在内存中。 当表的记录很大的时候,整个模型对象数组需要占用的空间可能会超过应用服务器的内存容量,导致应用或者服务器崩溃。
SendLog.find_each do | log |
# do something……
end
find_each 方法检索一批记录,然后逐一把每条记录作为模型传入块。在上面的例子中,find_each 方法取回 1000 条记录(find_each 和 find_in_batches 方法都默认一次检索 1000 条记录),然后逐一把每条记录作为模型传入块。这一过程会不断重复,直到完成所有记录的处理
查询语句样例:
2.2.0 :043 > send_logs = SendLog.where("record_date >=? and record_date <=? and user_id in (?)",20170409,20170508,[1,2,3,4])
2.2.0 :043 > send_logs.count
[Shard: slave1] (15.6ms) SELECT COUNT(*) FROM `send_logs` WHERE (record_date >=20170409 and record_date <=20170508 and user_id in (1,2,3,4))
=> 1732
2.2.0 :051 > send_logs.find_each do |log|
2.2.0 :052 > end
[Shard: slave1] SendLog Load (27.7ms) SELECT `send_logs`.* FROM `send_logs` WHERE (record_date >=20170409 and record_date <=20170508 and user_id in (1,2,3,4)) ORDER BY `send_logs`.`id` ASC LIMIT 1000
[Shard: slave1] SendLog Load (61894.6ms) SELECT `send_logs`.* FROM `send_logs` WHERE (record_date >=20170409 and record_date <=20170508 and user_id in (1,2,3,4)) AND (`send_logs`.`id` > 235181319) ORDER BY `send_logs`.`id` ASC LIMIT 1000
=> nil
以上产生的 SQL 语句可以看出,find_each 每次 limit 1000 条记录,由于 send_logs 的记录数为 1732 条,所以 send_logs.find_each 将会产生 2 条 SQL 查询语句
第一次 SQL 操作,执行时间,花费 27.7ms,而第二次查询操作耗时 61894.6ms
SendLog Load (61894.6ms) SELECT `send_logs`.* FROM `send_logs` WHERE (record_date >=20170409 and record_date <=20170508 and user_id in (1,2,3,4)) AND (`send_logs`.`id` > 235181319) ORDER BY `send_logs`.`id` ASC LIMIT 1000
以上耗时的 SQL,在前一条的基础上面,增加 AND (send_logs
.id
> 235181319) 的限制条件,用 explain 命令来查看 SQL 语句的执行情况,可以看到,该条查询没有用到索引,一次操作扫描了 1200 万条记录
mysql>explain SELECT `send_logs`.* FROM `send_logs` WHERE (record_date >=20170409 and record_date <=20170508 and user_id in (1,2,3,4)) AND (`send_logs`.`id` > 245317804) ORDER BY `send_logs`.`id` ASC LIMIT 1000\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: send_logs
type: range
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref:
rows: 12233849
Extra: Using where
而第一条语句是可以用到索引的
mysql>explain SELECT `send_logs`.* FROM `send_logs` WHERE (record_date >=20170409 and record_date <=20170508 and user_id in (1,2,3,4)) ORDER BY `send_logs`.`id` ASC LIMIT 1000\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: send_logs
type: range
possible_keys: index_send_logs_on_user_id
key: index_send_logs_on_user_id
key_len: 5
ref:
rows: 14292
Extra: Using index condition; Using where; Using filesort
实际的业务场景中,根据条件查询出来的数据量,最多只有数千条,所以直接用 each 方法,取代 find_each 方法,这样就一次性把少量的数据加载到内存中,对记录进行访问时,提升系统性能。
2.2.0 :066 > time = Benchmark.ms {
2.2.0 :067 > send_logs.each do |log|
2.2.0 :068 > end
2.2.0 :069?> }
=> 0.196017324924469
table
.id
asc limit 1000