SQL-Ausführung – Executor-Optimierung

In diesem technischen Beitrag wird hauptsächlich die Optimierung der Abfrageausführungs-Engine vorgestellt. Die Abfrageausführungs-Engine ist dafür verantwortlich, den vom SQL-Optimierer generierten Ausführungsplan zu interpretieren, die Daten aus der Speicher-Engine über die Aufgabenplanung zu lesen, die Ergebnismenge zu berechnen und sie dann an den Kunden zurückzugeben.

In den frühen Tagen der Entwicklung relationaler Datenbanken war der Anteil der Berechnungszeit an der Gesamtabfrage aufgrund der Einschränkungen der Computer-E/A-Funktionen nicht offensichtlich. Zu diesem Zeitpunkt lag der Schwerpunkt hauptsächlich auf der Abfrageoptimierung. Die Qualität des Optimierers ist für die Qualität des Ausführungsplans von großer Bedeutung. Die Rolle der Abfrageausführungs-Engine ist auf der entsprechenden Ebene bei der Datenbankoptimierung relativ schwach. Mit der Entwicklung der Computerhardware haben Abfrageausführungs-Engines jedoch nach und nach ihre wichtige Stellung unter Beweis gestellt.

In diesem Blog werden einige Quellcodes und Beispiele von KaiwuDB kombiniert, um vorzustellen, wie es die Fähigkeiten der zugrunde liegenden Hardware voll ausschöpfen und die Abfrageausführungs-Engine optimieren und so die Leistung des Datenbanksystems verbessern kann. Ob eine Abfrageausführungs-Engine effizient ist oder nicht, hängt direkt mit dem von ihr verwendeten Modell zusammen. Im Jahr 1990 wurde in der Arbeit „Volcano, an Extensible and Parallel Query Evaluation System“ das Vulkanmodell vorgeschlagen, das auch die Grundlage der KaiwuDB-Abfrageausführungs-Engine bildet .

1. Vulkanmodell/iteratives Modell

Als klassisches Abfrageausführungsmodell wird das Vulkanmodell von gängigen relationalen Datenbanken wie Oracle und MySQL übernommen. Dieses Modell abstrahiert jeden Operator in der relationalen Algebra in einen Operator (Iterator). Jeder Operator stellt eine Schnittstelle Next() bereit. Der Aufruf dieser Schnittstelle gibt eine vom Operator generierte/verarbeitete Datenzeile (Tupel) zurück. Durch Aufrufen von Next() am Wurzelknoten des Abfragebaums von oben nach unten werden die Daten von unten nach oben gezogen und verarbeitet, sodass das Vulkanmodell auch als Pull-Ausführungsmodell (Pull Based) bezeichnet wird.

 

Nehmen wir als Beispiel eine Abfrage, die zwei Tabellen verknüpft, und achten wir auf Schritt 4 in der Abbildung, den Select-Operator.

Rufen Sie die Methode Next() auf, um die nächste Zeile von ihrem Unteroperator anzufordern und zu prüfen, ob sie die Filterkriterien erfüllt. Wenn dies der Fall ist, wird die Zeile an ihren übergeordneten Operator zurückgegeben. Andernfalls wird die Zeile verworfen und der Vorgang wiederholt.

  // RunFilter runs a filter  expression and returns whether the filter passes.
   func RunFilter(filter tree.TypedExpr, evalCtx *tree.EvalContext)(bool, error){
		if filter == nil{
	   return true, nil
   
   }
   
   d, err := filter.Eval(evalCtx)
   if err != nil{
          return false,err
   }

    return d == tree.DBoolTrue, nil
}

Das Obige ist die Funktion, die KaiwuDB zum Filtern bei der Verarbeitung von Zeilen verwendet. Der Parameter Filtertyp ist tree.TypedExpr, was einen allgemeinen Ausdruck bedeutet. Das heißt, für jede Zeile wird ein Filter mit einem vollständig generischen Skalarausdruck aufgerufen. Der Ausdruck kann alles sein: Multiplikation, Division, Gleichheitsprüfung oder integrierte Funktion, es kann sogar ein Baum sein, der aus den oben genannten Ausdrücken besteht. Aufgrund dieser Allgemeingültigkeit muss der Computer beim Filtern jeder Zeile viel Arbeit leisten. Er muss prüfen, um welchen Ausdruck es sich handelt, bevor er Arbeiten ausführt. Dies entspricht der gleichen Logik wie bei interpretierten Sprachen (im Vergleich zu kompilierten Sprachen).

Obwohl das Vulkanmodell einfach, intuitiv und benutzerfreundlich ist, müssen die Operatoren nur frei zusammengestellt werden, und jeder Operator kümmert sich nur um seine eigene Verarbeitungslogik und ist sich der Ausführungs-Engine nicht bewusst. Allerdings verarbeitet die Iteration während des Ausführungsprozesses jeweils nur eine Datenzeile und die Datenlokalität ist schlecht, was leicht dazu führen kann, dass der CPU-Cache ungültig wird. Darüber hinaus wird die Next()-Funktion (virtuelle Funktion) zu oft aufgerufen , was teuer ist und die CPU-Ausführungseffizienz niedrig macht.

2. Operatorfusion

Durch die Integration häufig vorkommender Operatoren (z. B. Projekt und Filter) in andere Operatoren können virtuelle Funktionsaufrufe bis zu einem gewissen Grad reduziert und die Verarbeitungsfähigkeiten und Datenlokalität eines einzelnen Operators verbessert werden. Am Beispiel des Tablereader-Operators von KaiwuDB können Datenzeilen beim Scannen der Tabelle gefiltert und projiziert werden, und die entsprechende Logik wird in seiner Next()-Funktion implementiert.

Die folgende Abbildung ist ein vereinfachtes Zeitdiagramm des Funktionsaufrufs Next() des Tablereader-Operators. Es ist ersichtlich, dass beim Lesen eines Datenelements zur Verarbeitung Filter und Ausgabespalten beurteilt werden, um zu bestimmen, ob Filter und ausgeführt werden sollen Projektionsoperationen. Vereinfachtes Zeitdiagramm des Funktionsaufrufs Next() des TableReader-Operators

Die folgende Abbildung zeigt den physischen Plan einer Beispielabfrageanweisung. Sie können auch sehen, dass der Bereich Spans und die Ausgabespalte Out im TableReader-Operator eingeschränkt sind.

Beispielplan für die Abfrage

3. Vektorisierungsmodell

Anders als die zeilenweise Iteration des Vulkanmodells, wie in der folgenden Abbildung dargestellt, verwendet das Vektorisierungsmodell eine Batch-Iteration, um jeweils einen Datenstapel zwischen Operatoren zu übertragen. Nutzen Sie moderne CPUs effizienter, indem Sie die Datenrichtung ändern (von Zeilen zu Spalten) und die Konvertierung von Spalten zu Tupeln auf einen späteren Zeitpunkt verschieben. Kontinuierliche Daten begünstigen CPU-Cache-Treffer und reduzieren Speicherblockierungsphänomene. Darüber hinaus kann die Verarbeitung mehrerer Daten gleichzeitig durch SIMD-Anweisungen die Rechenleistung der CPU vollständig nutzen. Unterschiede in iterativen Daten zwischen Vulkanmodell und vektorisiertem Modell

Die Gesamtarchitektur des vektorisierten Modells ähnelt der des Vulkanmodells und verwendet immer noch ein Pull-Modell. Stellen Sie sich eine Tabelle „Personen“ mit den drei Spalten „ID“, „Name“ und „Alter“ vor. Der Stapel besteht aus einem ganzzahligen Array von „ID“, einem String-Array von „Name“ und einem ganzzahligen Array von „Alter“. Konfrontiert mit der Abfrage SELECT Name, (Alter – 30) * 50 AS-Bonus von Personen mit einem Alter von > 30; Das Vektorisierungsmodell ist in der folgenden Abbildung grob dargestellt. Vektorisierungsmodell

Offensichtlich können durch die Verwendung von vektorisierten Modellen und spaltenbasierter Speicherung bessere Ergebnisse erzielt werden, aber nicht spaltenbasierte Speicherung kann auch eine Kompromissmethode zur Implementierung des vektorisierten Modells verwenden. KaiwuDB verwendet eine Zeilenspeicher-Engine. In seinem vektorisierten Ausführungsmodell wird die Konvertierung mehrerer Zeilen in Vektorblöcke im zugrunde liegenden Operator implementiert. Der Operator der oberen Ebene verwendet Vektorblöcke als Eingabe für die Verarbeitung, und schließlich führt der Operator der obersten Ebene den Vektor aus Verarbeitung. Konvertierung von Block- in Zeilendaten.

Um außerdem den zusätzlichen Rechenaufwand zu vermeiden, der durch die Universalität der von Filter unter dem oben erwähnten Vulkanmodell verwendeten Skalarausdrücke verursacht wird, erlaubt im vektorisierten Ausführungsmodell von KaiwuDB jeder vektorisierte Operator keinen Freiheitsgrad oder Laufzeitwahl.

Das bedeutet, dass es für jede Kombination von Datentypen, Eigenschaften und Arbeitsaufgaben einen eigenen Operator gibt, der für die Arbeit verantwortlich ist. Die Ausführungs-Engine fordert Batches von der Operator-Kette an: Jeder Operator fordert einen Batch von seinem untergeordneten Operator an, führt seine spezifischen Arbeitsaufgaben aus und gibt den Batch an seinen übergeordneten Operator zurück.

Für die Beispielabfrage SELECT Name, (Age - 30) * 50 AS Bonus FROM People WHERE Age > 30; ist das tatsächliche Vektorisierungsmodell komplizierter als das obige, wie in der Abbildung unten gezeigt. Spezifisches Vektorisierungsmodell

Der SelectIntGreaterThanInt-Operator wählt alle Werte aus, deren Alter größer als 30 ist, nachdem er den Stapel der Personentabelle abgerufen hat. Anschließend wird dieser neue sel_age-Stapel an den ProjectSubIntInt-Operator übergeben, der eine einfache Subtraktion durchführt, um einen tmp-Stapel zu generieren. Schließlich wird dieser tmp-Stapel übergeben Zum ProjectMultIntInt-Operator berechnet dieser Operator den endgültigen Bonus = (Alter - 30) * 50.

Um diese vektorisierten Operatoren konkret zu implementieren, zerlegt KaiwuDB den Prozess in eine enge for-Schleife für eine einzelne Spalte. Der folgende Codeausschnitt (gekürzt) implementiert einige der Funktionen des SelectIntGreaterThanInt-Operators. Die Funktion ruft den Stapel von ihrem untergeordneten Operator ab und durchläuft jedes Element der Spalte, während sie den Wert prüft, der größer als 30 ist (p.constArg). Der Stapel und sein Auswahlvektor werden dann zur weiteren Verarbeitung an den übergeordneten Operator zurückgegeben. Dieser Code ist einfach, aber sehr effektiv. Die for-Schleife durchläuft ein Segment von int64, vergleicht jedes Slice-Element mit einer anderen int64-Konstante und speichert das Ergebnis in einem anderen int32-Slice, wodurch eine schnelle Schleife erreicht wird. .

func (p *selGTInt64Int64Const0p) Next(ctx context,Context) coldata,Batch {
// In order to inline the templated code of overloads, we need to have a
// 'decimalScratch' local variable of type 'decimalOverloadScratch'.
decimalScratch := p.decimalScratch
// However, the scratch is not used in all of the selection operators, so
// we add this to go around "unused" error.
_ = decimalScratch
for{
    batch := p.input.Next(ctx)
    if batch.Length() == 0 {
      return batch
}
vec := batch.ColVec(p.colIdx)
col := vec.Int64()
var idx int
n := batch.Length()
if sel := batch,Selection(); sel != nil {
  sel = sel[:n]
  for _, i := range sel {
    var cmp bool
    arg := col[i]
    {
      var cmpResult int
      {
        a, b := int64(arg), int64(p.constArg)
        if a < b {
          cmpResult = -1
        } else if a > b {
          cmpResult = 1 
        } else {
          cmpResult = 0
        }
      }
      cmp = cmpResult > 0
    }
    isNull := false
    if cmp && !isNull{
      sel[idx] = i
      idx++
    }
  }
}
if idx > 0 {
  batch.SetLength(idx)
  return batch
}
 }
 }

KaiwuDB verwendet die kv-Speicher-Engine rocksdb als zugrunde liegenden Speicher. Durch das Lesen von Zeilen aus dem Speicher, das Konvertieren von Zeilen in Stapel von Spaltendaten und das anschließende Senden dieser Stapel an die vektorisierte Ausführungs-Engine zur Verarbeitung kommt es bei der Verarbeitung großer Datenmengen zu einer erheblichen Leistungsverbesserung, bei großen Datenmengen hat die Vektorisierung jedoch keine Auswirkungen klein. Vorteile, da der Vektorisierungsprozess zusätzlichen Overhead mit sich bringt.

Daher können zeilenorientierte Ausführungsmodelle eine gute Leistung für OLTP-Abfragen (Online Transaction Processing) bieten, während vektorisierte Ausführungsmodelle häufig besser für OLAP-Abfragen (Online Analytical Processing) mit großen Datenmengen geeignet sind. Bei der Ausführung des Plans vergleicht KaiwuDB die geschätzte maximale Anzahl der vom Tablereader ausgegebenen Zeilen mit dem Feld VectorizeRowCountThreshold in SessionData, um festzustellen, ob eine Vektorisierung erforderlich ist.

KaiwuDB aktiviert die vektorisierte Ausführungs-Engine standardmäßig und Benutzer können sie auch deaktivieren. Das Ein- und Ausschalten der vektorisierten Ausführungs-Engine kann über SET eingestellt werden, wie in der folgenden Abbildung dargestellt. Darüber hinaus kann die EXPLAIN(VEC)-Anweisung verwendet werden, um den vektorisierten Ausführungsplan der Abfrage anzuzeigen.

Tang Xiaoou, Gründer von SenseTime, ist im Alter von 55 Jahren verstorben Im Jahr 2023 stagniert PHP Wi-Fi 7 wird vollständig verfügbar sein Anfang 2024 Debüt, fünfmal schneller als Wi-Fi 6 Das Hongmeng-System steht kurz vor der Unabhängigkeit und viele Universitäten haben „Hongmeng-Klassen“ eingerichtet Zhihui Das Startup-Unternehmen von Jun refinanziert sich, der Betrag übersteigt 600 Millionen Yuan und die Pre-Money-Bewertung beträgt 3,5 Milliarden Yuan Quark Browser PC-Version startet interne Tests KI-Code-Assistent ist beliebt, und Programmiersprachen-Rankings sind alle Es gibt nichts, was Sie tun können Das 5G-Modem und die Hochfrequenztechnologie des Mate 60 Pro liegen weit vorne MariaDB spaltet SkySQL auf und etabliert sich als unabhängiges Unternehmen Xiaomi antwortet auf Yu Chengdongs „Keel Pivot“-Plagiatsaussage von Huawei
{{o.name}}
{{m.name}}

Supongo que te gusta

Origin my.oschina.net/u/5148943/blog/10142778
Recomendado
Clasificación