2016-09-19 1 views
1

Ich versuche, ein Leistungsproblem zu lösen, wo wir eine WHERE IN-Klausel für eine Tonne nicht sequenzielle IDs ausführen. Gemäß this und dem MySQL-Leistungsbuch können Sie die Leistung beschleunigen, indem Sie eine temporäre Tabelle mit dem betreffenden Feld erstellen und sich der Tabelle anschließen, die Ihnen wichtig ist.Verzögern Sie eine temporäre Tabelle in MySQL mit ActiveRecord

Ich habe folgende Rails-Code innerhalb einer ActiveRecord::Base Klasse:

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 

Aber das Problem ist, dass dies eine SQL-Anweisung in Abhängigkeit von der Existenz einer temporären Tabelle erstellt, die mir in der Erklärung sicherzustellen, fallen zu lassen. Wenn ich die use-Anweisung entferne, funktioniert es gut, aber die temporäre Tabelle ist noch vorhanden.

In Anbetracht dieser Tatsache ist meine Frage:

Wie würde ich „verschieben“, um den Abwurf dieser Tabelle hinter die Tabellennamen auf einen Hintergrund Arbeiter knallen zu einem späteren Zeitpunkt fallen zu lassen?

ODER

Würde ich sicher sein, nicht auf den Tisch fallen und übernehmen nur die Verbindungspool die Verbindung ernten, wodurch die Tabelle schließlich fallen?

Antwort

0

So nach einem recht wenig Forschung, antwortete ich meine eigenen Fragen:

  1. Es gibt keine Möglichkeit, den Abwurf der Tabelle zu verschieben, aber ich war in der Lage, die die Beziehung zu zwingen, die zur Ausführung Abfrage jetzt mit der ActiveRecord::Relation#load Methode.

  2. In unserer Anwendung (und ich bin mir sicher, viele andere) wir Verbindungen zwischenspeichern für den späteren Gebrauch und selten recyceln sie, so nicht die Tabelle fallen würde eine sehr offensichtliche Speicherleck sein.

Ich landete statt AR Base in einer Util Klasse Codierung diese Methode up up:

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 

Das Problem oben gedreht, als ich meine Prämisse gebenchmarkt und widerlegte, dass diese Methode tatsächlich schneller sein würde. Ich benutzte den folgenden Benchmark-Code:

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 

Und die Ergebnisse waren ziemlich schlecht:

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

Aufgrund MySQL-Caching, die Methode, die ich versuchte, wesentlich langsamer über mehrere Iterationen ist. Es gibt wahrscheinlich andere Benchmarks, die ich ausprobieren könnte, aber derzeit scheint es, dass diese Optimierung es nicht wert ist.

Oh well ¯\_(ツ)_/¯

Verwandte Themen