使用ActiveRecord推迟在MySQL中删除临时表

时间:2016-09-19 23:51:31

标签: mysql ruby-on-rails ruby activerecord

我试图解决性能问题,我们在大量非顺序ID上运行WHERE IN子句。根据{{​​3}}和Performance MySQL一书,您可以通过创建一个包含相关字段的临时表并加入您关注的表来加快性能。

我在ActiveRecord::Base类中有以下Rails代码:

def self.where_in(field, ids)
  tmp_table = "tmp_table_#{SecureRandom.uuid.gsub('-', '_')}"
  begin
    # Create temporary table with one column
    connection.execute("CREATE TEMPORARY TABLE #{tmp_table} (param INT NOT NULL PRIMARY KEY) ENGINE=Memory")

    # Insert ids into the table (doesn't have to be ids)
    vals = ids.map{|i| "(#{i})"}.join(", ")
    connection.execute("INSERT INTO #{tmp_table} (param) VALUES #{vals};")

    # Return the join relation which is the same as WHERE IN (...)
    return self.joins("INNER JOIN #{tmp_table} on #{field} = #{tmp_table}.param").all
  ensure
    # Drop table after we're done...this is the problem
    connection.execute("DROP TEMPORARY TABLE IF EXISTS #{tmp_table}")
  end
end

但问题是,这会创建一个SQL语句,该语句依赖于我在ensure语句中删除的临时表的存在。如果我删除了ensure语句,它工作正常,但临时表仍然存在。

鉴于此,我的问题是:

我将如何推迟"删除这个表不会将表名弹出到后台工作者上以便以后删除吗?

OR

我是否可以安全地不丢弃表并假设连接池将获得连接,从而最终丢弃表?

1 个答案:

答案 0 :(得分:0)

经过相当多的研究后,我回答了自己的问题:

  1. 没有办法推迟删除表,但是,我现在可以使用ActiveRecord::Relation#load方法强制关系执行查询。

  2. 在我们的应用程序中(我相信很多其他人)我们缓存连接供以后使用,很少回收它们,所以不丢弃表将是一个非常明显的内存泄漏。

  3. 我最终在Util类而不是AR base中编写了这个方法:

    def self.where_in(collection, field, params)
      tmp_table = "tmp_table_#{SecureRandom.uuid.gsub('-', '_')}"
      collection.connection.execute("CREATE TEMPORARY TABLE #{tmp_table} (param INT NOT NULL PRIMARY KEY) ENGINE=Memory")
    
      vals = params.map{|i| "(#{i})"}.join(", ")
      collection.connection.execute("INSERT INTO #{tmp_table} (param) VALUES #{vals};")
    
      records = collection.joins("INNER JOIN #{tmp_table} on #{field} = #{tmp_table}.param").load
    
      yield records if block_given?
    
      collection.connection.execute("DROP TEMPORARY TABLE IF EXISTS #{tmp_table}")
      return records.to_a
    end
    

    当我对我的前提进行基准测试并且反驳这个方法实际上会更快时,问题就出现了。我使用了以下基准代码:

    Benchmark.bm do |x|
      x.report { 1000.times { Thing.where(id: refs).count } }
      x.report { 1000.times { Util.where_in(Thing, :id, refs) {|o| o.count }}}
    end
    

    结果非常糟糕:

       user     system      total        real
    0.940000   0.050000   0.990000 (  1.650669)
    8.950000   0.260000   9.210000 ( 12.201616)
    

    由于MySQL缓存,我尝试的方法在多次迭代中要慢得多。我可能会尝试其他基准测试,但目前看来这种优化并不值得。

    哦好¯\_(ツ)_/¯