Wie Daten aus schnell db lesen?



  • So, kurzes Feedback. Ich optimiere noch fleißig und muss dabei feststellen, dass der Query Planer von Postgres nicht immer die beste Strategie wählt. Die schnellste Lösung für PostgreSQL 9.1.13 ist im Moment, alle Abfrage separat in temporäre Tabellen zu schreiben und anschließend auszulesen.

    SELECT m.* FROM measurements m INTO TEMP TABLE tmp_measurements WHERE...
    SELECT f.* FROM measurement_files f INTO TEMP TABLE tmp_files WHERE f.measurement_id = f.measurement_id
    ...
    

    Damit wird das alles schon deutlich schneller. Was mich nur etwas wundert ist, dass das Erzeugen und anschließendes SELECT auf zwei temp. Tabellen 2-3x so schnell ist wie die Abfrage über die "echten" Tabellen. Hängt vermutlich iwie mit Plattenzugriffen oder Caching zusammen.

    -- "echte" Abfrage dauert ca. ~1:30 mit 1.7M Treffern
    SELECT 
      m.val1,
      m.val2,
      r.val1,
      r.val2
    FROM
      measurement_data m,
      measurement_results r
    WHERE
      m.result_id = r.result_id AND
      EXISTS( SELECT 1 FROM tmp_files WHERE m.file_id = tmp_files.file_id );
    
    -- Erzeugen temp. Tabellen und anschließende Abfrage dauert ~0:35 mit 1.7M Treffern
    SELECT 
      m.*
    INTO TEMP TABLE 
       tmp_data 
    FROM
       measurement_data m
    WHERE EXISTS( SELECT 1 FROM tmp_files WHERE m.file_id = tmp_files.file_id );
    
    SELECT
       r.*
    INTO TEMP TABLE 
       tmp_result
    FROM
       measurement_results r
    WHERE EXISTS( SELECT 1 FROM tmp_data WHERE r.result_id = tmp_data.result_id );
    
    SELECT 
      m.val1,
      m.val2,
      r.val1,
      r.val2
    FROM
      tmp_data d,
      tmp_result r
    WHERE
      m.result_id = r.result_id;
    


  • Der Grund ist das WHERE beim Erzeugen der Temp-Tables.
    Dadurch muss der JOIN dann nicht mehr die kompletten Tabellen joinen, sondern nur mehr das was nach dem WHERE übrig bleibt.

    Bei der "normalen" Abfrage scheint Postgres das falschrum zu machen, also erstmal alles zu JOINen und dann erst zu filtern.



  • So, neues Update:
    Dank eurer Hilfe habe ich die Performance enorm steigern können, die Laufzeit einer Abfrage ist von 240 Sekunden auf 35 Sekunden gefallen. 7 Sekunden davon entfallen auf eine 3rd Party Komponente, die nach einer Abfrage wohl noch Aufräumarbeiten durchführen muss und dazu ewig braucht. Hab den Hersteller mal angeschrieben und ein Ticket aufgemacht, mal sehen, was dabei rauskommt.
    Parallelisieren lässt sich das Ganze nicht, da die temporären Tabellen sessionbezogen sind und nur für die Erzeuger-Session sichtbar sind. Die db Zugriffskomponenten lassen den Zugriff nur in dem Thread zu, in dem sie erzeugt worden sind, also müsste ich für jeden Thread eine neue db Session erzeugen, die dann aber die temporären Tabellen nicht sehen. Und persistente Tabellen zu erzeugen zieht wieder andere Problem nach sich, die 3 Sekunden Laufzeitgewinn IMHO nicht rechtfertigen.

    Edit:
    Die o.g. Abfrage stellt das Maximum dessen dar, was praxistauglich ist. IdR laufen die Abfragen wesentlich kürzer und sind nach ca. 12-15 Sekunden zurück.



  • Cool!
    Würde mich interessieren wie die aktuelle Lösung jetzt aussieht.



  • Die Daten werden nach wie vor zweistufig gelesen.

    Stufe 1)
    Bestimmen der Messungen-Treffermenge.
    Die alte Lösung brauchte noch ein DISTINCT in der Abfrage, was irre zeit gefressen hat. Die neue Variante kommt ohne aus und hat dadurch schon im Worst Case 80 Sekunden gewonnen). Sie trägt alle Treffer in eine temporäre Tabelle ein.

    DROP TABLE IF EXISTS tmp_measurements;
    DROP TABLE IF EXISTS tmp_files;
    DROP TABLE IF EXISTS tmp_tables;
    DROP TABLE IF EXISTS tmp_data;
    DROP TABLE IF EXISTS tmp_custom;
    DROP TABLE IF EXISTS tmp_material;
    DROP TABLE IF EXISTS tmp_process;
    
    CREATE TEMPORARY TABLE tmp_measurements (LIKE measurements) WITH (FILLFACTOR = 100);
    CREATE TEMPORARY TABLE tmp_files        (LIKE measurement_files) WITH (FILLFACTOR = 100);
    CREATE TEMPORARY TABLE tmp_tables
    (
       measurement_id        INTEGER,
       file_id               INTEGER,
       measurement_result_id INTEGER,
       measurement_name      TEXT
    ) WITH (FILLFACTOR = 100);
    
    CREATE TEMPORARY TABLE tmp_data
    (
       measurement_id        INTEGER,
       file_id               INTEGER,
       measurement_result_id INTEGER,
       measurement_name      TEXT,
       measurement_data_id   INTEGER,
       item_name             TEXT,
       value                 REAL,
       unit                  TEXT
    ) WITH (FILLFACTOR = 100);
    
    CREATE TEMPORARY TABLE tmp_custom
    (
       measurement_id      INTEGER,
       file_id             INTEGER,
       custom_data_id      INTEGER,
       user_id             INTEGER,
       timestamp           TIMESTAMP(0) WITHOUT TIME ZONE,
       key                 TEXT,
       value               TEXT
    ) WITH (FILLFACTOR = 100);
    
    CREATE TEMPORARY TABLE tmp_material
    (
       measurement_id      INTEGER,
       file_id             INTEGER,
       material_data_id    INTEGER,
       material_id         INTEGER,
       material            TEXT
    ) WITH (FILLFACTOR = 100);
    
    CREATE TEMPORARY TABLE tmp_process
    (
       measurement_id      INTEGER,
       file_id             INTEGER,
       process_data_id     INTEGER,
       item_id             INTEGER,
       item_value          REAL
    ) WITH (FILLFACTOR = 100);
    
    INSERT INTO tmp_measurements
    SELECT m.* FROM measurements m
    WHERE
    (-- BEGIN SYSTEM FILTERS
       ( -- BEGIN SYSTEM FILTER GROUP Vordefinierter Filter (Zeitraum)
          EXISTS( SELECT 1 FROM measurements WHERE
             measurements.measurement_id = m.measurement_id AND
             measurements.start_time > '2016-08-22 00:00:00' AND
             measurements.start_time < '2016-08-23 00:00:00'
          )
       ) -- END SYSTEM FILTER GROUP Vordefinierter Filter (Zeitraum)
       AND
       ( -- BEGIN SYSTEM FILTER GROUP Vordefinierter Filter (Mount Point)
          EXISTS( SELECT 1 FROM measurement_files WHERE
             measurement_files.measurement_id = m.measurement_id AND
             measurement_files.mount_point_id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
          )
       ) -- END SYSTEM FILTER GROUP Vordefinierter Filter (Mount Point)
    )-- END SYSTEM FILTERS
    ORDER BY
       m.start_time DESC
    LIMIT 2001;
    
    SELECT
       tm.measurement_id,
       tm.measurement_name,
       tm.start_time,
       tm.stop_time,
       tm.comment,
       tm.measurement_guid
    FROM
       tmp_measurements tm
    

    Stufe 2)
    Bestimmung der Messdateien und aller Detaildaten. In Schritt 1) wurden bereits alle notwendigen temporären Tabellen erzeugt, in Schritt 2) werden sie befüllt. Die temp. Tabellen enthalten für jede Zeile zusätzlich die ID der Zeile aus der "Parent" Tabelle, damit die finalen Datentabellen keine Daten mehr aus anderen Tabellen benötigen. Was mich dabei nur wirklich wundert ist, dass das Erzeugen, Füllen und Auslesen der temp. Tabellen schneller geht als das direkte Auslesen der Daten per WHERE Klausel aus zwei Tabellen, denn es wird prinzipiell das Gleiche gemacht.

    INSERT INTO tmp_files
    SELECT mf.* FROM measurement_files mf
    WHERE
    (
       EXISTS( SELECT 1 FROM tmp_measurements
    				WHERE
                  mf.measurement_id = tmp_measurements.measurement_id
       )
    );
    
    INSERT INTO tmp_tables
    SELECT
      tf.measurement_id,
      tf.file_id,
      mr.measurement_result_id,
      mr.measurement_name
    FROM
       tmp_files tf,
       measurement_results mr
    WHERE
       tf.file_id = mr.file_id;
    
    INSERT INTO tmp_data
    SELECT
       tt.measurement_id,
       tt.file_id,
       tt.measurement_result_id,
       tt.measurement_name,
       md.measurement_data_id,
       md.item_name,
       md.value,
       md.unit
    FROM
       tmp_tables tt,
       measurement_data md
    WHERE
       tt.measurement_result_id = md.measurement_result_id;
    
    INSERT INTO tmp_custom
    SELECT
       tf.measurement_id,
       tf.file_id,
       cd.custom_data_id,
       cd.user_id,
       cd.timestamp,
       cd.key,
       cd.value
    FROM
       tmp_files tf,
       custom_data cd
    WHERE
       tf.file_id = cd.file_id;
    
    INSERT INTO tmp_process
    SELECT
       tf.measurement_id,
       tf.file_id,
       pd.process_data_id,
       pd.item_id,
       pd.item_value
    FROM
       tmp_files tf,
       process_data pd
    WHERE
       tf.file_id = pd.file_id;
    
    INSERT INTO tmp_material
    SELECT
       tf.measurement_id,
       tf.file_id,
       ma.material_data_id,
       ma.material_id,
       ma.material
    FROM
       tmp_files tf,
       material_data ma
    WHERE
       tf.file_id = ma.file_id;
    
    SELECT
       tf.file_id,
       tf.measurement_id,
       tf.mount_point_id,
       tf.file_type,
       tf.file_name,
       tf.comment,
       tf.file_status,
       tf.file_version,
       tf.archive_name,
       tf.file_size,
       tf.file_guid,
       tf.file_flags
    FROM
       tmp_files tf
    


  • Danke.

    DocShoe schrieb:

    Was mich dabei nur wirklich wundert ist, dass das Erzeugen, Füllen und Auslesen der temp. Tabellen schneller geht als das direkte Auslesen der Daten per WHERE Klausel aus zwei Tabellen, denn es wird prinzipiell das Gleiche gemacht.

    Nur im Prinzip, oder ist das SELECT wirklich identisch?
    Bei identischen SELECTs würde mich das nämlich etwas wundern.


Anmelden zum Antworten