Bester Index für die Ähnlichkeitsfunktion

8

Ich habe also diese Tabelle mit 6,2 Millionen Datensätzen und muss Suchabfragen mit Ähnlichkeit für einen für die Spalte durchführen. Die Abfragen können sein:

 SELECT  "lca_test".* FROM "lca_test"
 WHERE (similarity(job_title, 'sales executive') > 0.6)
 AND worksite_city = 'los angeles' 
 ORDER BY salary ASC LIMIT 50 OFFSET 0

Weitere Bedingungen können im where hinzugefügt werden (Jahr = X, Baustelle_Zustand = N, Status = 'zertifiziert', Visa_Klasse = Z).

Das Ausführen einiger dieser Abfragen kann sehr lange dauern, über 30 Sekunden. Manchmal länger als eine Minute.

EXPLAIN ANALYZE der zuvor erwähnten Abfrage gibt mir Folgendes:

Limit  (cost=0.43..42523.04 rows=50 width=254) (actual time=9070.268..33487.734 rows=2 loops=1)
->  Index Scan using index_lca_test_on_salary on lca_test  (cost=0.43..23922368.16 rows=28129 width=254) (actual time=9070.265..33487.727 rows=2 loops=1)
>>>> Filter: (((worksite_city)::text = 'los angeles'::text) AND (similarity((job_title)::text, 'sales executive'::text) > 0.6::double precision))
>>>> Rows Removed by Filter: 6330130 Total runtime: 33487.802 ms
Total runtime: 33487.802 ms

Ich kann nicht herausfinden, wie ich meine Spalte indizieren soll, damit sie blitzschnell ist.

EDIT: Hier ist die Postgres-Version:

PostgreSQL 9.3.5 unter x86_64-unknown-linux-gnu, kompiliert von gcc (Debian 4.7.2-5) 4.7.2, 64-Bit

Hier ist die Tabellendefinition:

                                                         Table "public.lca_test"
         Column         |       Type        |                       Modifiers                       | Storage  | Stats target | Description
------------------------+-------------------+-------------------------------------------------------+----------+--------------+-------------
 id                     | integer           | not null default nextval('lca_test_id_seq'::regclass) | plain    |              |
 raw_id                 | integer           |                                                       | plain    |              |
 year                   | integer           |                                                       | plain    |              |
 company_id             | integer           |                                                       | plain    |              |
 visa_class             | character varying |                                                       | extended |              |
 employement_start_date | character varying |                                                       | extended |              |
 employement_end_date   | character varying |                                                       | extended |              |
 employer_name          | character varying |                                                       | extended |              |
 employer_address1      | character varying |                                                       | extended |              |
 employer_address2      | character varying |                                                       | extended |              |
 employer_city          | character varying |                                                       | extended |              |
 employer_state         | character varying |                                                       | extended |              |
 employer_postal_code   | character varying |                                                       | extended |              |
 employer_phone         | character varying |                                                       | extended |              |
 employer_phone_ext     | character varying |                                                       | extended |              |
 job_title              | character varying |                                                       | extended |              |
 soc_code               | character varying |                                                       | extended |              |
 naic_code              | character varying |                                                       | extended |              |
 prevailing_wage        | character varying |                                                       | extended |              |
 pw_unit_of_pay         | character varying |                                                       | extended |              |
 wage_unit_of_pay       | character varying |                                                       | extended |              |
 worksite_city          | character varying |                                                       | extended |              |
 worksite_state         | character varying |                                                       | extended |              |
 worksite_postal_code   | character varying |                                                       | extended |              |
 total_workers          | integer           |                                                       | plain    |              |
 case_status            | character varying |                                                       | extended |              |
 case_no                | character varying |                                                       | extended |              |
 salary                 | real              |                                                       | plain    |              |
 salary_max             | real              |                                                       | plain    |              |
 prevailing_wage_second | real              |                                                       | plain    |              |
 lawyer_id              | integer           |                                                       | plain    |              |
 citizenship            | character varying |                                                       | extended |              |
 class_of_admission     | character varying |                                                       | extended |              |
Indexes:
    "lca_test_pkey" PRIMARY KEY, btree (id)
    "index_lca_test_on_id_and_salary" btree (id, salary)
    "index_lca_test_on_id_and_salary_and_year" btree (id, salary, year)
    "index_lca_test_on_id_and_salary_and_year_and_wage_unit_of_pay" btree (id, salary, year, wage_unit_of_pay)
    "index_lca_test_on_id_and_visa_class" btree (id, visa_class)
    "index_lca_test_on_id_and_worksite_state" btree (id, worksite_state)
    "index_lca_test_on_lawyer_id" btree (lawyer_id)
    "index_lca_test_on_lawyer_id_and_company_id" btree (lawyer_id, company_id)
    "index_lca_test_on_raw_id_and_visa_and_pw_second" btree (raw_id, visa_class, prevailing_wage_second)
    "index_lca_test_on_raw_id_and_visa_class" btree (raw_id, visa_class)
    "index_lca_test_on_salary" btree (salary)
    "index_lca_test_on_visa_class" btree (visa_class)
    "index_lca_test_on_wage_unit_of_pay" btree (wage_unit_of_pay)
    "index_lca_test_on_worksite_state" btree (worksite_state)
    "index_lca_test_on_year_and_company_id" btree (year, company_id)
    "index_lca_test_on_year_and_company_id_and_case_status" btree (year, company_id, case_status)
    "index_lcas_job_title_trigram" gin (job_title gin_trgm_ops)
    "lca_test_company_id" btree (company_id)
    "lca_test_employer_name" btree (employer_name)
    "lca_test_id" btree (id)
    "lca_test_on_year_and_companyid_and_wage_unit_and_salary" btree (year, company_id, wage_unit_of_pay, salary)
Foreign-key constraints:
    "fk_rails_8a90090fe0" FOREIGN KEY (lawyer_id) REFERENCES lawyers(id)
Has OIDs: no
bl0b
quelle
Es sollte offensichtlich sein, mindestens die Tabellendefinition (mit genauen Datentypen und Einschränkungen) und Ihre Version von Postgres anzugeben. Beachten Sie die Anweisungen in den Tag-Informationen zur Postgresql-Leistung . Stellen Sie auch klar, ob immer eine Gleichheitsbedingung erfüllt ist worksite_city.
Erwin Brandstetter
Vielen Dank, ich habe meinen Beitrag so bearbeitet, dass er diese Informationen enthält. Und ja , es ist immer eine Gleichheitsbedingung auf worksite_city, worksite_state, yearund / oder status
BL0B

Antworten:

14

Sie haben vergessen zu erwähnen, dass Sie das zusätzliche Modul installiert haben pg_trgm, das die similarity()Funktion bereitstellt .

Ähnlichkeitsoperator %

Was auch immer Sie tun, verwenden Sie zunächst den Ähnlichkeitsoperator %anstelle des Ausdrucks (similarity(job_title, 'sales executive') > 0.6). Viel billiger. Die Indexunterstützung ist an Operatoren in Postgres gebunden , nicht an Funktionen.

Um die gewünschte minimale Ähnlichkeit von zu erhalten 0.6, führen Sie Folgendes aus:

SELECT set_limit(0.6);

Die Einstellung bleibt für den Rest Ihrer Sitzung erhalten, sofern sie nicht auf etwas anderes zurückgesetzt wird. Überprüfen Sie mit:

SELECT show_limit();

Dies ist etwas ungeschickt, aber großartig für die Leistung.

Einfacher Fall

Wenn Sie nur die besten Übereinstimmungen in der Spalte job_titlefür die Zeichenfolge "Vertriebsleiter" wünschen, ist dies ein einfacher Fall für die Suche nach "nächsten Nachbarn" und könnte mit einem GiST-Index unter Verwendung der Trigrammoperatorklasse gist_trgm_ops(jedoch nicht mit einem GIN-Index) gelöst werden. ::

CREATE INDEX trgm_idx ON lcas USING gist (job_title gist_trgm_ops);

Um auch eine Gleichheitsbedingung einzuschließen worksite_city, benötigen Sie das zusätzliche Modul btree_gist. Ausführen (einmal pro DB):

CREATE EXTENSION btree_gist;

Dann:

CREATE INDEX lcas_trgm_gist_idx ON lcas USING gist (worksite_city, job_title gist_trgm_ops);

Abfrage:

SELECT set_limit(0.6);  -- once per session

SELECT *
FROM   lca_test
WHERE  job_title % 'sales executive'
AND    worksite_city = 'los angeles' 
ORDER  BY (job_title <-> 'sales executive')
LIMIT  50;

<-> als "Distanz" -Operator:

eins minus den similarity()Wert.

Postgres kann auch zwei separate Indizes kombinieren, einen einfachen btree-Index worksite_cityund einen separaten GiST-Index. job_titleDer mehrspaltige Index sollte jedoch am schnellsten sein - wenn Sie die beiden Spalten in Abfragen regelmäßig wie folgt kombinieren.

Dein Fall

Ihre Abfrage wird salaryjedoch nach Entfernung und Ähnlichkeit sortiert , wodurch sich die Art des Spiels vollständig ändert. Jetzt können wir sowohl den GIN- als auch den GiST-Index verwenden, und GIN wird schneller sein (noch mehr in Postgres 9.4, das die GIN-Indizes stark verbessert hat - Hinweis!).

Ähnliches gilt für die zusätzliche Gleichheitsprüfung worksite_city: Installieren Sie das Zusatzmodul btree_gin. Ausführen (einmal pro DB):

CREATE EXTENSION btree_gin;

Dann:

CREATE INDEX lcas_trgm_gin_idx ON lcas USING gin (worksite_city, job_title gin_trgm_ops);

Abfrage:

SELECT set_limit(0.6);  -- once per session

SELECT *
FROM   lca_test
WHERE  job_title % 'sales executive'
AND    worksite_city = 'los angeles' 
ORDER  BY salary 
LIMIT  50 -- OFFSET 0

Auch dies sollte (weniger effizient) mit dem einfacheren Index funktionieren, den Sie bereits haben ( "index_lcas_job_title_trigram"), möglicherweise in Kombination mit anderen Indizes. Die beste Lösung hängt vom Gesamtbild ab.

Nebenbei

  • Sie haben viele Indizes. Sind Sie sicher, dass sie alle in Gebrauch sind und ihre Wartungskosten bezahlen?

  • Sie haben einige zweifelhafte Datentypen:

    employement_start_date | character varying
    employement_end_date   | character varying

    Scheint so zu sein date. Usw.

Verwandte Antworten:

Erwin Brandstetter
quelle
Ich habe "index_lcas_job_title_trigram" gin (job_title gin_trgm_ops)irgendwo gelesen, dass Gin schneller ist als das Wesentliche. Ist das wahr?
11.
1
@ bl0b, Gin wird überhaupt nicht unterstützt similarity, daher ist es für diesen Zweck nicht schneller.
Jjanes
@ bl0b: Während jjanes Recht hat (und das war auch meine erste Idee), ist Ihr Fall anders und Sie können schließlich einen GIN-Index verwenden. Ich habe viel mehr hinzugefügt.
Erwin Brandstetter
@ErwinBrandstetter vielen Dank für die Antwort! Kurze Frage: Sie sagen, dass GIN schneller ist und dass ich installieren sollte btree_gin. Aber dann sagen Sie bei der Indexerstellung, dass Sie Folgendes ausführen sollen: CREATE INDEX lcas_trgm_gin_idx ON lcas USING gist (worksite_city, job_title gist_trgm_ops);Nur ein Tippfehler?
Bl0b
1
@ErwinBrandstetter Ging von 30s bis 6seconds. Tolle Verbesserungen! Vielen Dank!
Bl0b