0

下面是从 Internet 下载大约 9000 行的 txt 文件并填充数据库的代码,我已经尝试了很多,但它需要超过 7 分钟的时间。我正在使用 win 7 64 位和 ruby​​ 1.9.3。有没有办法更快??

require 'open-uri'
require 'dbi'
dbh = DBI.connect("DBI:Mysql:mfmodel:localhost","root","")
#file = open('http://www.amfiindia.com/spages/NAV0.txt')
file = File.open('test.txt','r')
lines = file.lines
2.times { lines.next }
curSubType = ''
curType = ''
curCompName = ''
lines.each do |line|
    line.strip!
if line[-1] == ')'
    curType,curSubType = line.split('(')
    curSubType.chop!
elsif line[-4..-1] == 'Fund'
    curCompName = line.split(" Mutual Fund")[0] 
elsif line == ''
    next
else
    sCode,isin_div,isin_re,sName,nav,rePrice,salePrice,date = line.split(';')
    sCode = Integer(sCode)
    sth = dbh.prepare "call mfmodel.populate(?,?,?,?,?,?,?)"
    sth.execute curCompName,curSubType,curType,sCode,isin_div,isin_re,sName
end
end
dbh.do "commit"
dbh.disconnect
file.close


106799;-;-;HDFC ARBITRAGE FUND RETAIL PLAN DIVIDEND OPTION;10.352;10.3;10.352;29-Jun-2012

这是要插入到表中的数据格式。现在有 8000 行这样的行,我怎样才能通过组合所有这些来进行插入并只调用一次该过程。此外,mysql 是否支持数组和迭代在例程中做这样的事情。请提出您的建议。谢谢。

编辑

我必须根据它们是否已经存在来插入表,我还需要在插入表之前使用条件比较。我肯定不会为这些写SQL语句,所以我写了SQL存储过程。现在我有一个列表@the_data,我如何将它传递给过程,然后在 MySQL 端遍历它。有任何想法吗 ?

insert into mfmodel.company_masters (company_name) values
#{@the_data.map {|str| "('#{str[0]}')"}.join(',')}

这进行了 100 次插入,但其中 35 次是多余的,因此我需要在插入之前在表中搜索现有条目。

有任何想法吗 ?谢谢

4

2 回答 2

5

从您的评论来看,您似乎将所有时间都花在执行数据库查询上。在最近的一个 Ruby 项目中,我还必须优化一些将 CSV 文件中的数据导入数据库的慢代码。通过使用单个批量INSERT查询导入所有数据,而不是对 CSV 文件的每一行进行 1 个查询,我的性能提高了大约 500 倍。我将所有数据累积在一个数组中,然后使用字符串插值和Array#join.

从您的评论来看,您似乎不知道如何为批量构建和执行动态 SQL INSERT。首先将数据放入嵌套数组中,并以已知顺序插入字段。举个例子,假设我们有这样的数据:

some_data = [['106799', 'HDFC FUND'], ['112933', 'SOME OTHER FUND']]

您似乎正在使用 Rails 和 MySQL,因此动态 SQL 必须使用 MySQL 语法。要构建和执行INSERT,您可以执行以下操作:

ActiveRecord::Base.connection.execute(<<SQL)
  INSERT INTO some_table (a_column, another_column)
  VALUES #{some_data.map { |num,str| "(#{num},'#{str}')" }.join(',')};
SQL

您说您需要将数据插入到 2 个不同的表中。这不是问题。只需将每个表的数据累积在不同的数组中,然后执行 2 个动态查询,可能在事务中。2 个查询将比 9000 快得多。

同样,您在评论中说您可能需要更新一些记录而不是插入。我上面提到的“CSV 导入”案例也是如此。解决方案只是稍微复杂一点:

# sometimes code speaks more eloquently than prose
require 'set'
already_imported = Set.new
MyModel.select("unique_column_which_also_appears_in_imported_files").each do |x|
  already_imported << x.unique_column_which_also_appears_in_imported_files
end

to_insert,to_update = [],[]
imported_data.each do |row|
  # for the following line, don't let different data types 
  #   (like String vs. Numeric) get ya
  # if you need to convert the imported data to match correctly against what's
  #   already in the DB, do it!
  if already_imported.include? row[index_of_unique_column]
    to_update << row
  else
    to_insert << row
  end
end

然后你必须为所涉及的每个表构建一个动态INSERT 一个动态UPDATE。如果您需要,请在Google 上搜索UPDATE语法,并使用所有您最喜欢的字符串处理函数!

回到上面的示例代码,请注意数字字段和字符串字段之间的区别。如果字符串可能包含单引号,则必须确保所有单引号都被转义。String#gsub当您尝试执行此操作时,的行为可能会让您感到惊讶:它为 赋予了特殊含义\'。到目前为止,我发现转义单引号的最佳方法是:string.gsub("'") { "\\'" }. 也许其他海报知道更好的方法。

如果要插入日期,请​​确保将它们转换为 MySQL 的日期语法。

是的,我知道“自己动手”的 SQL 清理非常有问题。上述方法甚至可能存在安全漏洞;如果是这样,我希望我的消息灵通的同龄人能够让我直截了当。但是性能提升太大了,不容忽视。同样,如果这可以使用带有占位符的准备好的查询来完成,并且您知道如何,请发布!

查看您的代码,您似乎正在使用存储过程 ( mfmodel.populate) 插入数据。即使您确实想为此使用存储过程,为什么还要dbh.prepare在循环中?您应该能够将该行移到lines.each.

于 2012-07-01T13:38:58.980 回答
1

您可能想尝试将数据导出为 csv 并使用“加载数据文件...替换”加载它。它似乎比尝试构建批量插入查询更清洁/更容易。

于 2012-07-02T00:41:57.037 回答