Frage

Wir haben zwei Tabellen wie diese:

Event
    id
    type
    ... a bunch of other columns

ProcessedEvent
    event_id
    process

Es sind Indizes definiert für

  • Ereignis(id) (PK)
  • ProcessedEvent (event_id, Prozess)

Die erste stellt Ereignisse in einer Anwendung dar.

Die zweite stellt die Tatsache dar, dass ein bestimmtes Ereignis Prozesse durch einen bestimmten Prozess erhalten hat.Da es viele Prozesse gibt, die ein bestimmtes Ereignis verarbeiten müssen, gibt es für jeden Eintrag in der ersten Tabelle mehrere Einträge in der zweiten Tabelle.

Um alle Ereignisse zu finden, die verarbeitet werden müssen, führen wir die folgende Abfrage aus:

select * // of course we do name the columns in the production code
from Event
where type in ( 'typeA', 'typeB', 'typeC')
and id not in (
    select event_id
    from ProcessedEvent
    where process = :1  
)

Die Statistiken sind aktuell

Da die meisten Ereignisse verarbeitet werden, sollte der beste Ausführungsplan meiner Meinung nach in etwa so aussehen

  • Vollständiger Indexscan für den ProcessedEvent-Index
  • Vollständiger Indexscan für den Ereignisindex
  • Anti-Join zwischen den beiden
  • Tischzugriff mit dem Rest
  • Filter

Stattdessen macht Oracle Folgendes

  • Vollständiger Indexscan für den ProcessedEvent-Index
  • Vollständiger Tabellenscan für die Ereignistabelle
  • Filtern Sie die Ereignistabelle
  • Anti-Join zwischen den beiden Sätzen

Mit einem Indexhinweis veranlasse ich Oracle, Folgendes zu tun:

  • Vollständiger Indexscan für den ProcessedEvent-Index
  • Vollständiger Indexscan für den Ereignisindex
  • Tabellenzugriff auf die Ereignistabelle
  • Filtern Sie die Ereignistabelle
  • Anti-Join zwischen den beiden Sätzen

Das ist meiner Meinung nach wirklich dumm.

Meine Frage ist also:Was könnte der Grund dafür sein, dass Oracle auf dem frühen Tabellenzugriff besteht?


Zusatz:Die Leistung ist schlecht.Wir beheben das Leistungsproblem, indem wir nur die Event.IDs auswählen und dann die benötigten Zeilen „manuell“ abrufen.Aber das ist natürlich nur ein Workaround.

War es hilfreich?

Lösung

Ihr VOLLSTÄNDIGER INDEX-SCAN wird wahrscheinlich schneller sein als ein VOLLSTÄNDIGER TABLE-SCAN, da der Index wahrscheinlich „dünner“ als die Tabelle ist.Dennoch ist der VOLLSTÄNDIGE INDEX-SCAN eine vollständige Segmentablesung und kostet ungefähr die gleichen Kosten wie der VOLLSTÄNDIGE TABLE-SCAN.

Sie fügen jedoch auch einen TABLE ACCESS BY ROWID-Schritt hinzu.Es ist ein teurer Schritt:ein logisches IO pro Zeile für den ROWID-Zugriff, während Sie ein logisches IO erhalten pro Multiblock (abhängig von Ihrem db_file_multiblock_read_count parameter) für den VOLLSTÄNDIGEN TABELLE-SCAN.

Zusammenfassend berechnet der Optimierer Folgendes:

cost(FULL TABLE SCAN) < cost(FULL INDEX SCAN) + cost(TABLE ACCESS BY ROWID)

Aktualisieren:Der FULL TABLE SCAN aktiviert den Filter nach Typ auch früher als im FULL INDEX SCAN-Pfad (da der INDEX nicht weiß, um welchen Typ es sich bei einem Ereignis handelt), wodurch die Größe des Satzes reduziert wird, der anti-joined wird (ein weiterer Vorteil). des FULL TABLE SCAN).

Andere Tipps

Der Optimierer macht viele Dinge, die zunächst keinen Sinn ergeben, aber es hat seine Gründe.Das ist vielleicht nicht immer der Fall Rechts, aber sie sind verständlich.

Aufgrund ihrer Größe ist es möglicherweise einfacher, die Ereignistabelle vollständig zu scannen als über den Zeilen-ID-Zugriff.Möglicherweise sind deutlich weniger E/A-Vorgänge erforderlich, um die gesamte Tabelle sequenziell zu lesen, als um einzelne Teile zu lesen.

Ist die Leistung schlecht oder fragen Sie sich nur, warum der Optimierer das getan hat?

Ich kann das Verhalten des Optimierers nicht erklären, aber ich habe die Erfahrung gemacht, „NOT IN“ um jeden Preis zu vermeiden und es stattdessen durch MINUS zu ersetzen, etwa so:

select * from Event
where id in (
  select id from Event where type in ( 'typeA', 'typeB', 'typeC')
 minus
  select id from ProcessedEvent
)

Ich habe bei ähnlichen Transformationen Größenordnungen bei der Abfrageleistung gesehen.

Etwas wie:

WITH
  PROCEEDED AS
  (
    SELECT
      event_id
    FROM
      ProcessedEvent
    WHERE
      PROCESS = :1
  )
SELECT
  * // of course we do name the columns in the production code
FROM
  EVENT
LEFT JOIN PROCEEDED P
ON
  p.event_id = EVENT.event_id
WHERE
  type           IN ( 'typeA', 'typeB', 'typeC')
  AND p.event_id IS NULL; -- exclude already proceeded

könnte schnell genug arbeiten (zumindest viel schneller als NOT IN).

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top