Mit Oracle Database 18c gibt es jetzt auch einen neuen Memory Bereich - der Memory-optimierte Rowstore (auch MemOptimized Rowstore).
Konzipiert für hohe Abfrageleistungen (Beispiel Internet of Things (IoT) Workloads) sorgt er für sehr schnelle und latenzarme Abfragen. Key-Value-Lookups
auf Basis von Primärschlüsselwerten (mit Abfragefilter "Spalte = Wert") nutzen direkt einen Memory Hash-Index
bei der Ausführung. Das neue Attribut auf Tabellenebene - MEMOPTIMIZE FOR READ - gibt an, welche der Tabellen mit dem neuen Memory Hash-Index in den Buffer Cache gepinnt werden sollen.
Die Memory-Architektur sieht dann folgendermaßen aus. (Bildquelle: Database Concepts Guide)
Eine kurze Erklärung zu der Graphik:
Der MemOptimized Rowstore besteht aus zwei Teilen - dem MemOptimized Buffer Cache und einem Hash-Index.
Um Platten-I/O zu vermeiden, werden die Buffer einer MEMOPTIMIZE FOR READ definierten Tabelle in den MemOptimized-Pool gepinnt, solange bis die Tabelle wieder als
NO MEMOPTIMIZE FOR READ markiert wird. Der MemOptimized Pool verwendet dabei die gleiche Struktur wie der Datenbank Buffer Cache. Er ist aber vollständig vom
Datenbank Buffer Cache getrennt. 75% des MemOptimized Pool werden dabei vom MemOptimized Buffer Cache belegt; die restlichen 25% verwendet der Hash-Index.
Der Hash-Index ist eine nicht persistente Segmentstruktur, die aus mehreren, nicht zusammenhängenden Speichereinheiten besteht.
Jede Speichereinheit enthält dabei eine Anzahl von Hash-Buckets. Eine spezielle Mapping Struktur sorgt dann für die Abbildung einer Speichereinheit
auf den zugehörigen Primärschlüssel.
Wichtig zu wissen ist sicherlich, dass beim Einsatz dieser Funktion keine Änderungen innerhalb von Anwendungen nötig ist. Aber wie funktioniert das Ganze?
MemOptimized Rowstore aktivieren
Um den Memoptimized Pool zu aktivieren, wird der Initialisierungsparameter MEMOPTIMIZE_POOL_SIZE auf einen ganzzahligen Wert gesetzt
(der Pool ist standardmäßig deaktiviert). Die Größe des Memoptimized Pool ist ein fester Wert: Um die Größe des MemOptimized Pools zu ändern, müß der Wert von
MEMOPTIMIZE_POOL_SIZE manuell geändert und die Datenbankinstanz neu gestartet werden. Die Poolgröße kann nicht dynamisch mit ALTER SYSTEM geändert werden.
Überprüfen wir zuerst unsere Umgebung und setzen danach den Wert für unsere Tests auf 500MB. Das bedeutet 500MB der SGA werden nun ausschließlich für den MemOptimized Rowstore verwendet.
Der minimale Wert ist übrigens 100MB.
SQL> select value, ISSES_MODIFIABLE, ISSYS_MODIFIABLE, ISPDB_MODIFIABLE from v$parameter where upper(name)='MEMOPTIMIZE_POOL_SIZE'; VALUE ISSES ISSYS_MOD ISPDB --------------- ----- --------- ----- 0 FALSE IMMEDIATE FALSE SQL> ALTER SYSTEM SET MEMOPTIMIZE_POOL_SIZE = 500M SCOPE=SPFILE; System altered.
Damit der Wert angenommen wird, muss die Datenbank neu gestartet werden. Wir überprüfen danach die Memory Einstellung.
SQL> SHOW PARAMETER MEMOPTIMIZE_POOL_SIZE NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ memoptimize_pool_size big integer 512M
MemOptimized Rowstore verwenden
Ähnlich wie beim Feature "In-Memory External Tables" (siehe auch Blogeintrag "In-Memory External Tables") sind auch hier zwei Schritte nötig, um den neuen Memorybereich zu verwenden:
Der Hash-Index wird erzeugt und wird automatisch von der Oracle Datenbank gepflegt. Keine weiteren Operationen sind erforderlich.
Verbinden wir uns als User SH mit der PDB PDB1 und erzeugen eine Testtabelle SALES_TAB mit Daten aus der Tabelle SALES.
Wichtige Voraussetzung ist dabei das Vorhandensein eines Primary Keys, ansonsten lässt sich das Feature nicht einschalten.
SQL> create table sales_tab (sales_id number(6) primary key, prod_id NUMBER(6) not null, cust_id NUMBER not null, time_id DATE not null, quantity_sold NUMBER(3) not null, amount_sold NUMBER(10,2) not null); Table created. SQL> insert into sales_tab (sales_id,prod_id, cust_id, time_id, quantity_sold, amount_sold) select rownum, PROD_ID, CUST_ID, TIME_ID, QUANTITY_SOLD, AMOUNT_SOLD from sales; 960 rows created. SQL> execute dbms_stats.gather_table_stats('SH','SALES_TAB'); PL/SQL procedure successfully completed. SQL> ALTER TABLE SH.SALES_TAB MEMOPTIMIZE FOR READ; Table altered. SQL> select MEMOPTIMIZE_READ, inmemory from user_tables where table_name='SALES_TAB'; MEMOPTIM INMEMORY -------- -------- ENABLED DISABLED
Wie man erkennen kann, wird nur der MemOptimized Rowstore und nicht der In-Memory Columnstore verwendet - beide sind unabhängig voneinander.
Ausschalten lässt sich das Feature übrigens mit ALTER TABLE ... NO MEMOPTIMIZE FOR READ.
Im letzten Schritt muß der Rowstore befüllt werden. Dies wird mit der Prozedur DBMS_MEMOPTIMIZE.POPULATE durchgeführt.
SQL> execute dbms_memoptimize.populate(schema_name=>'SH',table_name=>'SALES_TAB'); PL/SQL procedure successfully completed.
Nun wollen wir das Feature testen und lassen uns dazu den Ausführungsplan anzeigen.
SQL> set linesize window SQL> set autotrace on explain SQL> select * from sales_tab where sales_id=5; SALES_ID PROD_ID CUST_ID TIME_ID QUANTITY_SOLD AMOUNT_SOLD ---------- ---------- ---------- --------- ------------- ----------- 5 1900 84910 06-JAN-98 42 378 Execution Plan ---------------------------------------------------------- Plan hash value: 2899464683 ------------------------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 1 | 74 | 2 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID READ OPTIM| SALES_TAB | 1 | 74 | 2 (0)| 00:00:01 | |* 2 | INDEX UNIQUE SCAN READ OPTIM | PK_SALES_ID | 1 | | 1 (0)| 00:00:01 | ------------------------------------------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("SALES_ID"=5)
Die zwei neuen Operationen für den Tabellen- und den Index- Zugriff - TABLE ACCESS BY INDEX ROWID READ OPTIM und INDEX UNIQUE SCAN READ OPTIM - zeigen
an, dass der MemOptimized Rowstore verwendet wird.
Hinweis: Die neue 18c SQL*Plus Formatvorgabe SET LINESIZE WINDOW liefert eine gute Unterstützung bei der Ausgabe von Ausführungsplänen.
Verändern wir nun die Tabelle, indem wir eine Zeile mit SALES_ID 961 hinzufügen und die TIME_ID der Zeile mit SALES_ID 5 verändern.
SQL> insert into sales_tab values (961, 11160, 17450, sysdate, 20, 800); 1 row created. SQL> update sales_tab set time_id=sysdate where sales_id=5; 1 row updated. SQL> commit; Commit complete.
Auch hier wird offensichtlich der MemOptimized Rowstore verwendet. Der Rowstore mit Hash-Index und MemOptimized Buffer Cache wird dabei automatisch von der Oracle Datenbank gepflegt.
SQL> set autotrace on explain SQL> set linesize window SQL> select * from sales_tab where sales_id=5 SALES_ID PROD_ID CUST_ID TIME_ID QUANTITY_SOLD AMOUNT_SOLD ---------- ---------- ---------- --------- ------------- ----------- 5 1900 84910 01-JUN-18 42 378 Execution Plan ---------------------------------------------------------- Plan hash value: 1909132751 ------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 28 | 2 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID READ OPTIM| SALES_TAB | 1 | 28 | 2 (0)| 00:00:01 | |* 2 | INDEX UNIQUE SCAN READ OPTIM | SYS_C0022714 | 1 | | 1 (0)| 00:00:01 | ------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("SALES_ID"=5) SQL> select * from sales_tab where sales_id=961; SALES_ID PROD_ID CUST_ID TIME_ID QUANTITY_SOLD AMOUNT_SOLD ---------- ---------- ---------- --------- ------------- ----------- 961 11160 17450 01-JUN-18 20 800 Execution Plan ---------------------------------------------------------- Plan hash value: 1909132751 ------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 28 | 2 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID READ OPTIM| SALES_TAB | 1 | 28 | 2 (0)| 00:00:01 | |* 2 | INDEX UNIQUE SCAN READ OPTIM | SYS_C0022714 | 1 | | 1 (0)| 00:00:01 | ------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("SALES_ID"=961)
Betrachten wir noch weitere Zugriffe.
SQL> set linesize window SQL> set autotrace traceonly explain SQL> select * from sales_tab where sales_id between 1 and 5; Execution Plan ---------------------------------------------------------- Plan hash value: 3154829518 --------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 5 | 140 | 3 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID BATCHED| SALES_TAB | 5 | 140 | 3 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | PK_SALES_ID | 5 | | 2 (0)| 00:00:01 | --------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("SALES_ID">=1 AND "SALES_ID"<=5) SQL> select * from sales_tab where sales_id>5; Execution Plan ---------------------------------------------------------- Plan hash value: 3500109886 ------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 956 | 26768 | 3 (0)| 00:00:01 | |* 1 | TABLE ACCESS FULL| SALES_TAB | 956 | 26768 | 3 (0)| 00:00:01 | ------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("SALES_ID">5) SQL> select * from sales_tab where prod_id=11160 and sales_id=1; Execution Plan ---------------------------------------------------------- Plan hash value: 2899464683 ------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 28 | 2 (0)| 00:00:01 | |* 1 | TABLE ACCESS BY INDEX ROWID| SALES_TAB | 1 | 28 | 2 (0)| 00:00:01 | |* 2 | INDEX UNIQUE SCAN | PK_SALES_ID | 1 | | 1 (0)| 00:00:01 | ------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("PROD_ID"=11160) 2 - access("SALES_ID"=1)
Wie gut zu erkennen ist, wurde der MemOptimized Rowstore nur bei Abfragen der Form "sales_id =" verwendet. Bei allen anderen Abfragearten werden andere
Ausführungspläne verwendet.
Bei weiteren Tests sind mir einige Einschränkungen im aktuellen Release aufgefallen, die in der folgenden Aufzählung gelistet sind.
Leider findet man im Moment noch nicht zu allen Punkten einen Eintrag im Handbuch.
Lizenzierung
Das Feature MemOptimized Rowstore ist im Moment auf Exadata und dem Oracle Database Cloud Service "Enterprise Edition Extreme Performance" verfügbar. Genaueres kann man im Handbuch Licensing Information User Manual nachlesen.
Weitere Informationen
Zurück zur Community-Seite