Database
 sql >> Datenbank >  >> RDS >> Database

Virtuelle Spalten und funktionale Indizes

Viel zu oft sehen wir schlecht geschriebene komplexe SQL-Abfragen, die gegen die Datenbanktabellen laufen. Die Ausführung solcher Abfragen kann sehr kurz oder sehr lange dauern, aber sie verbrauchen eine enorme Menge an CPU und anderen Ressourcen. Dennoch liefern komplexe Abfragen in vielen Fällen wertvolle Informationen für die Anwendung/Person. Daher bringt es nützliche Vorzüge in alle Arten von Anwendungen.

Komplexität der Abfragen

Schauen wir uns die problematischen Abfragen genauer an. Viele davon sind komplex. Das kann mehrere Gründe haben:

  1. Der für die Daten gewählte Datentyp;
  2. Die Organisation und Speicherung der Daten in der Datenbank;
  3. Transformation und Verknüpfung der Daten in einer Abfrage, um die gewünschte Ergebnismenge abzurufen.

Sie müssen diese drei Schlüsselfaktoren richtig durchdenken und richtig implementieren, damit Abfragen optimal funktionieren.

Es kann jedoch sowohl für Datenbankentwickler als auch für DBAs zu einer nahezu unmöglichen Aufgabe werden. Beispielsweise kann es außerordentlich schwierig sein, neue Funktionen zu bestehenden Legacy-Systemen hinzuzufügen. Ein besonders komplizierter Fall ist, wenn Sie die Daten aus einem Altsystem extrahieren und transformieren müssen, damit Sie sie mit den Daten vergleichen können, die von dem neuen System oder der neuen Funktionalität erzeugt werden. Sie müssen dies erreichen, ohne die Funktionalität der Legacy-Anwendung zu beeinträchtigen.

Solche Abfragen können komplexe Verknüpfungen beinhalten, wie die folgenden:

  1. Eine Kombination aus Teilstring und/oder Verkettung mehrerer Datenspalten;
  2. Integrierte Skalarfunktionen;
  3. Angepasste UDFs;
  4. Jede Kombination aus Vergleichen von WHERE-Klauseln und Suchbedingungen.

Abfragen haben, wie oben beschrieben, normalerweise komplexe Zugriffspfade. Was noch schlimmer ist, sie haben möglicherweise viele Tabellen-Scans und/oder vollständige Index-Scans mit solchen Kombinationen von JOINs oder Suchen, die auftreten.

Datentransformation und Manipulationen in Abfragen

Wir müssen darauf hinweisen, dass alle Daten, die dauerhaft in einer Datenbanktabelle gespeichert sind, irgendwann transformiert und/oder manipuliert werden müssen, wenn wir diese Daten aus der Tabelle abfragen. Die Transformation kann von einer einfachen bis zu einer sehr komplexen Transformation reichen. Je nachdem, wie komplex sie sein mag, kann die Transformation viel CPU und Ressourcen verbrauchen.

In den meisten Fällen erfolgen Transformationen in JOINs, nachdem die Daten gelesen und in die tempdb ausgelagert wurden Datenbank (SQL Server) oder workfile database / temp-tablespaces wie in anderen Datenbanksystemen.

Da die Daten in der Arbeitsdatei nicht indexierbar sind , steigt die Zeit, die zum Ausführen kombinierter Transformationen und JOINs benötigt wird, exponentiell an. Die abgerufenen Daten werden größer. Dadurch entwickeln sich daraus resultierende Abfragen durch zusätzliches Datenwachstum zu einem Performance-Flaschenhals.

Wie kann also ein Datenbankentwickler oder ein DBA diese Leistungsengpässe schnell beheben und sich gleichzeitig mehr Zeit verschaffen, um die Abfragen für eine optimale Leistung neu zu konstruieren und neu zu schreiben?

Es gibt zwei Möglichkeiten, solche hartnäckigen Probleme effektiv zu lösen. Eine davon ist die Verwendung virtueller Spalten und/oder funktionaler Indizes.

Funktionale Indizes und Abfragen

Normalerweise erstellen Sie Indizes für Spalten, die entweder einen eindeutigen Satz von Spalten/Werten in einer Zeile angeben (eindeutige Indizes oder Primärschlüssel) oder einen Satz von Spalten/Werten darstellen, die in WHERE-Klausel-Suchbedingungen einer Abfrage verwendet werden oder verwendet werden können.

Wenn Sie solche Indizes nicht eingerichtet haben und wie zuvor beschrieben komplexe Abfragen entwickelt haben, werden Sie Folgendes bemerken:

  1. Reduktion des Leistungsniveaus bei Verwendung der Erklärung Abfrage und Anzeigen von Tabellenscans oder vollständigen Indexscans
  2. Sehr hohe CPU- und Ressourcenauslastung durch Abfragen;
  3. Lange Ausführungszeiten.

Moderne Datenbanken lösen diese Probleme normalerweise dadurch, dass Sie eine funktionale erstellen können oder funktionsbasiert index, wie in SQLServer, Oracle und MySQL (v 8.x) benannt. Oder es kann Index on sein Ausdruck/ausdrucksbasiert Indizes, wie in anderen Datenbanken (PostgreSQL und Db2).

Angenommen, Sie haben eine Purchase_Date-Spalte vom Datentyp TIMESTAMP oder DATETIME in Ihrer Bestellung Tabelle, und diese Spalte wurde indiziert. Wir beginnen mit der Abfrage der Order Tabelle mit einer WHERE-Klausel:

SELECT ...
FROM Order
WHERE DATE(Purchase_Date) = '03.12.2020'

Diese Transaktion führt zum Scannen des gesamten Index. Wenn die Spalte jedoch nicht indiziert wurde, erhalten Sie einen Tabellenscan.

Nachdem der gesamte Index gescannt wurde, wird dieser Index in tempdb / workfile verschoben (ganze Tabelle wenn Sie einen Tabellenscan erhalten ) vor dem Abgleich mit dem Wert 03.12.2020 .

Da eine große Bestelltabelle viel CPU und Ressourcen verbraucht, sollten Sie einen funktionalen Index mit dem DATE-Ausdruck (Purchase_Date ) als eine der Indexspalten und unten gezeigt:

CREATE ix_DatePurchased on sales.Order(Date(Purchase_Date) desc, ... )

Dabei machen Sie das Matching-Prädikat DATE (Purchase_Date) =‚03.12.2020‘ indexierbar. Anstatt also den Index oder die Tabelle vor dem Abgleich des Werts in die tempdb / workfile zu verschieben, machen wir den Index nur teilweise zugänglich und/oder scannen. Dies führt zu einer geringeren CPU- und Ressourcenauslastung.

Schauen Sie sich ein anderes Beispiel an. Es gibt einen Kunden Tabelle mit den Spalten Vorname, Nachname . Diese Spalten werden wie folgt indiziert:

CREATE INDEX ix_custname on Customer(first_name asc, last_name asc),

Außerdem haben Sie eine Ansicht, die diese Spalten zu customer_name verkettet Spalte:

CREATE view v_CustomerInfo( customer_name, .... ) as
select first_name ||' '|| last_name as customer_name,.....
from Customer
where ...

Sie haben eine Abfrage von einem E-Commerce-System, das nach dem vollständigen Kundennamen sucht:

select c.*
from v_CustomerInfo c
where c.customer_name = 'John Smith'
....

Auch diese Abfrage erzeugt einen vollständigen Index-Scan. Im schlimmsten Fall handelt es sich um einen vollständigen Tabellenscan, bei dem alle Daten aus dem Index oder der Tabelle vor der Verkettung von first_name in die Arbeitsdatei verschoben werden und Nachname Spalten und Übereinstimmung mit dem Wert „John Smith“.

Ein anderer Fall ist das Erstellen eines Funktionsindex wie unten gezeigt:

CREATE ix_fullcustname on sales.Customer( first_name ||' '|| last_name desc, ... )

Auf diese Weise können Sie die Verkettung in der Ansichtsabfrage zu einem indexierbaren Prädikat machen. Anstelle eines vollständigen Indexscans oder Tabellenscans haben Sie einen partiellen Indexscan. Eine solche Abfrageausführung führt zu einer geringeren CPU- und Ressourcennutzung, schließt die Arbeit in der Arbeitsdatei aus und gewährleistet so eine schnellere Ausführungszeit.

Virtuelle (generierte) Spalten und Abfragen

Generierte Spalten (virtuelle Spalten oder berechnete Spalten) sind Spalten, die die spontan generierten Daten enthalten. Die Daten können nicht explizit auf einen bestimmten Wert gesetzt werden. Es bezieht sich auf die Daten in anderen Spalten, die in einer DML-Abfrage abgefragt, eingefügt oder aktualisiert wurden.

Die Wertegenerierung solcher Spalten wird auf der Grundlage eines Ausdrucks automatisiert. Diese Ausdrücke könnten Folgendes generieren:

  1. Eine Folge ganzzahliger Werte;
  2. Der Wert basiert auf den Werten anderer Spalten in der Tabelle;
  3. Es kann Werte generieren, indem integrierte Funktionen oder benutzerdefinierte Funktionen (UDFs) aufgerufen werden.

Es ist ebenso wichtig zu beachten, dass diese Spalten in einigen Datenbanken (SQLServer, Oracle, PostgreSQL, MySQL und MariaDB) so konfiguriert werden können, dass sie die Daten entweder dauerhaft mit der Ausführung der INSERT- und UPDATE-Anweisungen speichern oder den zugrunde liegenden Spaltenausdruck im laufenden Betrieb ausführen wenn wir die Tabelle abfragen und die Spalte Speicherplatz sparen.

Wenn der Ausdruck jedoch kompliziert ist, wie bei komplexer Logik in der UDF-Funktion, sind die Einsparungen bei Ausführungszeit, Ressourcen und CPU-Abfragekosten möglicherweise nicht so hoch wie erwartet.

Daher können wir die Spalte so konfigurieren, dass sie das Ergebnis des Ausdrucks dauerhaft in einer INSERT- oder UPDATE-Anweisung speichert. Dann erstellen wir einen regulären Index für diese Spalte. Auf diese Weise sparen wir die CPU, die Ressourcennutzung und die Ausführungszeit der Abfrage. Auch hier kann es je nach Komplexität des Ausdrucks zu einer geringfügigen Steigerung der INSERT- und UPDATE-Leistung kommen.

Schauen wir uns ein Beispiel an. Wir deklarieren die Tabelle und erstellen einen Index wie folgt:

CREATE TABLE Customer as (
  customerID Int GENERATED ALWAYS AS IDENTITY,
  first_name VARCHAR(50) NOT NULL,
  last_name VARCHAR(50) NOT NULL,
  customer_name as (first_name ||' '|| last_name) PERSISTED
  ...
  );
CREATE ix_fullcustname on sales.Customer( customer_name desc, ... )

Auf diese Weise verschieben wir die Verkettungslogik aus der Ansicht im vorherigen Beispiel nach unten in die Tabelle und speichern die Daten persistent. Wir rufen die Daten mithilfe eines übereinstimmenden Scans auf einem regulären Index ab. Es ist hier das bestmögliche Ergebnis.

Indem wir einer Tabelle eine generierte Spalte hinzufügen und einen regulären Index für diese Spalte erstellen, können wir die Transformationslogik nach unten auf die Tabellenebene verschieben. Hier speichern wir die transformierten Daten dauerhaft in Insert- oder Update-Anweisungen, die sonst in Abfragen transformiert würden. Die JOIN- und INDEX-Scans werden viel einfacher und schneller.

Funktionale Indizes, generierte Spalten und JSON

Globale Web- und mobile Anwendungen verwenden leichte Datenstrukturen wie JSON, um die Daten aus dem Web/mobilen Gerät in die Datenbank zu verschieben und umgekehrt. Der geringe Platzbedarf von JSON-Datenstrukturen macht die Datenübertragung über das Netzwerk schnell und einfach. Es ist einfach, JSON im Vergleich zu anderen Strukturen, z. B. XML, auf eine sehr kleine Größe zu komprimieren. Es kann Strukturen beim Parsing zur Laufzeit übertreffen.

Aufgrund der zunehmenden Verwendung von JSON-Datenstrukturen haben relationale Datenbanken das JSON-Speicherformat entweder als BLOB-Datentyp oder als CLOB-Datentyp. Diese beiden Typen machen die Daten in solchen Spalten so wie sie sind nicht indexierbar.

Aus diesem Grund haben die Datenbankanbieter JSON-Funktionen eingeführt, um JSON-Objekte abzufragen und zu modifizieren, da Sie diese Funktionen einfach in die SQL-Abfrage oder andere DML-Befehle integrieren können. Diese Abfragen hängen jedoch von der Komplexität der JSON-Objekte ab. Sie sind sehr CPU- und ressourcenintensiv, da BLOB- und CLOB-Objekte in den Arbeitsspeicher oder schlimmer noch in die Arbeitsdatei ausgelagert werden müssen vor Abfragen und/oder Manipulation.

Angenommen, wir haben einen Kunden Tabelle mit den Kundendetails Daten, die als JSON-Objekt in einer Spalte namens CustomerDetail gespeichert sind . Wir richten die Abfrage der Tabelle wie folgt ein:

SELECT CustomerID,
  JSON_VALUE(CustomerDetail, '$.customer.Name') AS Name,
  JSON_VALUE(CustomerDetail, '$.customer.Surname') AS Surname,
  JSON_VALUE(CustomerDetail, '$.customer.address.PostCode') AS PostCode,
  JSON_VALUE(CustomerDetail, '$.customer.address."Address Line 1"') + ' '
  + JSON_VALUE(CustomerDetail, '$.customer.address."Address Line 2"') AS Address,
  JSON_QUERY(CustomerDetail, '$.customer.address.Country') AS Country
FROM Customer
WHERE ISJSON(CustomerDetail) > 0
  AND JSON_VALUE(CustomerDetail, '$.customer.address.Country') = 'Iceland'
  AND JSON_VALUE(CustomerDetail, '$.customer.address.PostCode') IN (101,102,110,210,220)
  AND Status = 'Active'
ORDER BY JSON_VALUE(CustomerDetail, '$.customer.address.PostCode')

In diesem Beispiel fragen wir die Daten von Kunden ab, die in einigen Teilen der Hauptstadtregion in Island leben. Alle Aktiv Daten sollten in die Arbeitsdatei abgerufen werden bevor das Suchprädikat angewendet wird. Dennoch führt der Abruf zu einer zu hohen CPU- und Ressourcenauslastung.

Dementsprechend gibt es ein effektives Verfahren, um JSON-Abfragen schneller ablaufen zu lassen. Es beinhaltet die Nutzung der Funktionalität durch generierte Spalten, wie zuvor beschrieben.

Den Leistungsschub erreichen wir durch das Hinzufügen von generierten Spalten. Eine generierte Spalte würde das JSON-Dokument mithilfe der JSON-Funktionen nach bestimmten Daten durchsuchen, die in der Spalte dargestellt sind, und den Wert in der Spalte speichern.

Wir können diese generierten Spalten mit regulären SQL-Where-Klausel-Suchbedingungen indizieren und abfragen. Daher wird die Suche nach bestimmten Daten in JSON-Objekten sehr schnell.

Wir fügen zwei generierte Spalten hinzu – Land und Postleitzahl :

ALTER TABLE Customer
ADD Country as JSON_VALUE(CustomerDetail,'$.customer.address.Country');
ALTER TABLE Customer
ADD PostCode as JSON_VALUE(CustomerDetail,'$.customer.address.PostCode');

CREATE INDEX ix_CountryPostCode on Country(Country asc,PostCode asc);

Außerdem erstellen wir einen zusammengesetzten Index für die spezifischen Spalten. Jetzt können wir die Abfrage in das unten angezeigte Beispiel ändern:

SELECT CustomerID,
  JSON_VALUE(CustomerDetail, '$.customer.customer.Name') AS Name,
  JSON_VALUE(CustomerDetail, '$.customer.customer.Surname') AS Surname,
  JSON_VALUE(CustomerDetail, '$.customer.address.PostCode') AS PostCode,
  JSON_VALUE(CustomerDetail, '$.customer.address."Address Line 1"') + ' '
  + JSON_VALUE(CustomerDetail, '$.customer.address."Address Line 2"') AS Address,
  JSON_QUERY(CustomerDetail, '$.customer.address.Country') AS Country
FROM Customer
WHERE ISJSON(CustomerDetail) > 0
  AND Country = 'Iceland'
  AND PostCode IN (101,102,110,210,220)
  AND Status = 'Active'
ORDER BY JSON_VALUE(CustomerDetail, '$.customer.address.PostCode')

Dies beschränkt den Datenabruf auf aktive Kunden nur in einem Teil der isländischen Hauptstadtregion. Dieser Weg ist schneller und effizienter als die vorherige Abfrage.

Schlussfolgerung

Alles in allem können wir Probleme ziemlich schnell beseitigen, indem wir virtuelle Spalten oder funktionale Indizes auf Tabellen anwenden, die Schwierigkeiten verursachen (CPU- und ressourcenintensive Abfragen).

Virtuelle Spalten und funktionale Indizes können beim Abfragen komplexer JSON-Objekte helfen, die in regulären relationalen Tabellen gespeichert sind. Wir müssen die Probleme jedoch vorher sorgfältig bewerten und die erforderlichen Änderungen entsprechend vornehmen.

In einigen Fällen, wenn die Abfrage- und/oder JSON-Datenstrukturen sehr komplex sind, kann ein Teil der CPU- und Ressourcennutzung von den Abfragen auf die INSERT/UPDATE-Prozesse verlagert werden. Es gibt uns insgesamt weniger CPU- und Ressourceneinsparungen als erwartet. Wenn ähnliche Probleme auftreten, ist möglicherweise eine gründlichere Neugestaltung von Tabellen und Abfragen unvermeidlich.