Ruby 如何提高这段代码的执行速度?

chucai · 2012年08月10日 · 最后由 suupic 回复于 2012年09月13日 · 5181 次阅读

场景: 我需要从一个 txt 文件 (有 4000 行,每行只有一个手机号码) 读取文件中的手机号,并将其存入数据库中。最后,将手机号码和密码生成一个新的文件 output.txt 中。我的代码如下:

path   = File.join(Rails.root, "db/phones.txt")
output = File.join(Rails.root, "db/output.txt")
output_str = ""
File.open(path, "r").each do |line|
  phone = line.chomp
  output_str << phone
  user  = User.find_by_phone(phone)
  if user
    output_str << " " + "已注册\n"
  else
    user = User.simple_create(phone)
    output_str << " " + user.passwd + "\n"
  end 
end 
File.open(output, "w") do |f| 
  f << output_str
end 

这段代码执行时间大概在 9 分钟以上,如何能提高这段代码的执行效率?

用 SQLITE 可以用INSERT OR IGNORE一次插入很多条记录,问题是 SQLITE 一条语句参数个数是有上限的,一次插入 4000 可能已经超了。别的数据库应该也有类似的方法,且没参数个数的限制。

#1 楼 @bhuztez 谢谢!但是我需要最后的密码,且密码是随机的。

#2 楼 @chucai 用 SQL 生成随机数应该可以的。

#3 楼 @bhuztez 恩。但我生成以后,还需要取出密码和手机号,还要将这些存入一个文件。

#4 楼 @chucai 你就再用一个SELECT

数据库里面 users 表给 phone 增加索引

数据量太小了,文件操作完全可以依托内存,这样可以文件读写一次完成。 进一步,如果数据库数据不多,那么干脆一次性把 user 表全读出来,然后数组减数组,暴力解决有时候也是一个方案

#6 楼 @huacnlee 加索引会减慢写入速度,按 LZ 这个写法,加不加索引,关系不大。

#8 楼 @fsword 目前最大的问题是能用一句 SQL 搞定的他用了 4000 句

#8 楼 @fsword 是的 我看了下文件的大小 phones.txt 40 多 k output.txt 70 多 k

一次读入内存是完全没有问题 但是速度就是慢了点

不知道从文件读写上,是否还有优化的办法

#10 楼 @bhuztez 你指的是 find 么?这个要看场景,这个问题的前提是数据量比较小

#12 楼 @fsword 不是啊,无论多少数据量,你最终数据库写入的数据量是一样的,就是 4000 个手机号码。而现在 LZ 的问题是,SQL 语句数是O(n)的,而实际上是可以做到O(1)的。

手工加密密码,得到 phone,passwd 组合。最后用原生的 sql 语句一次性插入,和一次性写入文件?

4000 条记录 * 50ms / 1000 / 60 = 才 3 分钟 用了 9 分钟,那每条耗费 135ms

  • 数据库什么情况
  • Ruby 环境什么情况(难道你是 Windows?)

#15 楼 @huacnlee

Ubuntu 11.04 \n \l
mysql  Ver 14.14 Distrib 5.1.54, for debian-linux-gnu (i686) using readline 6.2

#13 楼 @bhuztez 4000 个不是全都写入,有判断的,所以我才说全内存操作,整理好数据以后一次性写入

循环中查数据库效率肯定不会太高啊,直接用 in 来查询,查到的结果就是数据库中已经存在了的

感觉楼主的问题有点诡异,还是老实一些,打几个点分析一下性能瓶颈,不知道原因容易瞎猜

匿名 #20 2012年08月10日

我觉得时间花的最多的就是 4000 次插入操作:

https://github.com/zdennis/activerecord-import

users = []
4000.times do
  users << User.new
end

User.import users

#17 楼 @fsword 我都说了用INSERT OR IGNORE之类的办法了,不需要自己判断,直接丢给数据库就好。

我觉得 4000 个查询和不到 4000 个插入就是小儿科,包个 transaction 就可以了 ...

4000 个查询,可以分成 40 个查询,每次 100 个?

Rails 的数组有方法 #in_groups_of#in_groups,可以给数据分组。分好组后,用select xxx where id in (a, b, c) 这种查询就减少查询次数了。

默认一次 insert 就是一次事务。如果把所有的 insert 用一个事物包起来,会快很多。前提是保证不要回滚啊。

#22 楼 @luikore 对,用个 transaction 就可以了。

除了数据库,我问个问题,楼主的 ruby 版本是什么?建议用最新的 1.9.3 试试? 4000 个 sql,不做什么优化,应该非常快的。 bechmark 下,看下瓶颈真正在哪里。

27 楼 已删除

要我说,直接用 hadoop 或者 twitterStorm..

@zhangyuan 恩,是这个思路,与@xdite 一篇文章中的思路一致

你的数据库里面,已有的数据量是多大?虽然你的 txt 文件数量只有 4000 行,但是你的 users 表的数据量可能不小啊?

分批提交是王道,性能能提高 XX 倍

需要 登录 后方可回复, 如果你还没有账号请 注册新账号