0

Hier ist die Abfrage wie es in Schienen:Schienen zu optimieren Struggling WHERE NOT IN-Abfrage in Rails

User.limit(20). 
    where.not(id: to_skip, number_of_photos: 0). 
    where(age: @[email protected]_age_max). 
    tagged_with(@user.seeking_traits, on: :trait, any: true). 
    tagged_with(@user.seeking_gender, on: :trait, any: true).ids 

Und hier ist der Ausgang des EXPLAIN ANALYZE. Beachten Sie, dass der Teil id <> ALL(...) verkürzt ist. Es gibt ungefähr 10Kids drin.

Limit (cost=23.32..5331.16 rows=20 width=1698) (actual time=2237.871..2243.709 rows=20 loops=1) 
    -> Nested Loop Semi Join (cost=23.32..875817.48 rows=3300 width=1698) (actual time=2237.870..2243.701 rows=20 loops=1) 
     -> Merge Semi Join (cost=22.89..857813.95 rows=8311 width=1702) (actual time=463.757..2220.691 rows=1351 loops=1) 
       Merge Cond: (users.id = users_trait_taggings_356a192.taggable_id) 
       -> Index Scan using users_pkey on users (cost=0.29..834951.51 rows=37655 width=1698) (actual time=455.122..2199.322 rows=7866 loops=1) 
        Index Cond: (id IS NOT NULL) 
        Filter: ((number_of_photos <> 0) AND (age >= 18) AND (age <= 99) AND (id <> ALL ('{7066,7065,...,15624,23254}'::integer[]))) 
        Rows Removed by Filter: 7652 
       -> Index Only Scan using taggings_idx on taggings users_trait_taggings_356a192 (cost=0.42..22767.59 rows=11393 width=4) (actual time=0.048..16.009 rows=4554 loops=1) 
        Index Cond: ((tag_id = 2) AND (taggable_type = 'User'::text) AND (context = 'trait'::text)) 
        Heap Fetches: 4554 
     -> Index Scan using index_taggings_on_taggable_id_and_taggable_type_and_context on taggings users_trait_taggings_5df4b2a (cost=0.42..2.16 rows=1 width=4) (actual time=0.016..0.016 rows=0 loops=1351) 
       Index Cond: ((taggable_id = users.id) AND ((taggable_type)::text = 'User'::text) AND ((context)::text = 'trait'::text)) 
       Filter: (tag_id = ANY ('{4,6}'::integer[])) 
       Rows Removed by Filter: 2 
Total runtime: 2243.913 ms 

Complete version here.

Es scheint, als ob etwas mit Index Scan using users_pkey on users nicht stimmt, wo der Index-Scan sehr lange dauert. Auch wenn es auf age ein Index ist, number_of_photos und die id:

add_index "users", ["age"], name: "index_users_on_age", using: :btree 
add_index "users", ["number_of_photos"], name: "index_users_on_number_of_photos", using: :btree 

to_skip ist ein Array von Benutzer-IDs nicht zu überspringen. A user hat viele skips. Jede skip hat eine partner_id.

So to_skip zu holen ich tue:

to_skip = @user.skips.pluck(:partner_id) 

Ich habe versucht, die Abfrage zu isolieren, um nur:

sql = User.limit(20). 
    where.not(id: to_skip, number_of_photos: 0). 
    where(age: @[email protected]_age_max).to_sql 

Und noch das gleiche Problem immer mit der Analyse erklären.

Limit (cost=0.00..435.34 rows=20 width=1698) (actual time=0.219..4.844 rows=20 loops=1) 
    -> Seq Scan on users (cost=0.00..819629.38 rows=37655 width=1698) (actual time=0.217..4.838 rows=20 loops=1) 
     Filter: ((id IS NOT NULL) AND (number_of_photos <> 0) AND (age >= 18) AND (age <= 99) AND (id <> ALL ('{7066,7065,...,15624,23254}'::integer[]))) 
     Rows Removed by Filter: 6 
Total runtime: 5.044 ms 

Complete version here: Auch hier ist die Liste der Benutzer-IDs snipped.

Irgendwelche Gedanken, wie ich diese Abfrage in Rails + Postgres optimieren kann?

EDIT: Hier sind die relevanten Modelle:

User model

class User < ActiveRecord::Base 
    acts_as_messageable required: :body, # default [:topic, :body] 
         dependent: :destroy 

    has_many :skips, :dependent => :destroy 

    acts_as_taggable # Alias for acts_as_taggable_on :tags 
    acts_as_taggable_on :seeking_gender, :trait, :seeking_race 
    scope :by_updated_date, -> { 
    order("updated_at DESC") 
    } 
end 

# schema 

create_table "users", force: :cascade do |t| 
    t.string "email", default: "", null: false 
    t.datetime "created_at", null: false 
    t.datetime "updated_at", null: false 
    t.text  "skips", array: true 
    t.integer "number_of_photos", default: 0 
    t.integer "age" 
end 

add_index "users", ["age"], name: "index_users_on_age", using: :btree 
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree 
add_index "users", ["number_of_photos"], name: "index_users_on_number_of_photos", using: :btree 
add_index "users", ["updated_at"], name: "index_users_on_updated_at", order: {"updated_at"=>:desc}, using: :btree 

Skips model

class Skip < ActiveRecord::Base 
    belongs_to :user 
end 

# schema 

create_table "skips", force: :cascade do |t| 
    t.integer "user_id" 
    t.integer "partner_id" 
    t.datetime "created_at", null: false 
    t.datetime "updated_at", null: false 
end 

add_index "skips", ["partner_id"], name: "index_skips_on_partner_id", using: :btree 
add_index "skips", ["user_id"], name: "index_skips_on_user_id", using: :btree 
+0

Bitte geben Sie den Code aller relevanten Modelle an. –

+0

Hinzugefügt sie. Bitte lassen Sie mich wissen, ob sie ausreichen. –

+0

Sie haben ein dediziertes 'Skip'-Modell und auch' Users.skips' Array-Feld. Was ist der Grund für Letzteres? –

Antwort

1

Die Geschwindigkeit Problem aufgrund der langen Liste der IDs in to_skip wahrscheinlich ist (ca. 60Kb) als Array übergeben werden. Die Lösung wäre dann, es als Ergebnis einer Unterabfrage zu überarbeiten, so dass die Nachbearbeitung die Abfrage besser optimieren könnte.

Wenn Sie to_skip erstellen, verwenden Sie select anstelle von pluck. pluck gibt ein Array zurück, das Sie dann an die Hauptabfrage übergeben. select gibt wiederum ActiveRecord::Relation zurück, dessen SQL-Wert in die Hauptabfrage aufgenommen werden kann, wodurch sie möglicherweise effizienter wird.

to_skip = @user.skips.select(:partner_id) 

Bis Ihr Modellcode veröffentlicht wird, ist es schwierig, spezifischere Vorschläge zu machen. Die allgemeine Richtung, die ich untersuchen würde, wäre es, alle relevanten Schritte in einer einzigen Abfrage zusammenzuführen, damit die Datenbank die Optimierungen durchführen kann.

UPDATE

Die aktive Abfrage Nimm select mit so etwas wie folgt aussehen würde (übersprungenen ich taggable Sachen wie es scheinbar nicht die Leistung nicht beeinträchtigt viel):

User.limit(20). 
    where.not(id: @user.skips.select(:partner_id), number_of_photos: 0). 
    where(age: 0..25) 

Dies ist die SQL-Abfrage das wird ausgeführt. Beachten Sie, wie IDs von einer Unterabfrage geholt werden überspringen:

SELECT "users".* FROM "users" 
    WHERE ("users"."number_of_photos" != 0) 
    AND ("users"."id" NOT IN (
     SELECT "skips"."partner_id" 
     FROM "skips" 
     WHERE "skips"."user_id" = 1 
    )) 
    AND ("users"."age" BETWEEN 0 AND 25) 
    LIMIT 20 

Versuchen Sie Ihre Abfrage auf diese Weise ausgeführt wird und sehen, wie sie die Leistung auswirkt.

+0

Danke. Ich werde es versuchen. In der Zwischenzeit habe ich auch das Modell Annotation + Schema hinzugefügt –

+0

Eigentlich, wie schreibe die Abfrage jetzt mit der Auswahl (: partner_id)? to_skip = @ user.skips.select (: partner_id) Ergebnisse = User.limit (20) .where.not (id: to_skip) .ids nicht –

+0

zu funktionieren scheint Bitte beachten Sie das Update auf meine Antwort . –