18c: MemOptimized Rowstore - der schnelle In-Memory Hash Index Zugriff

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:

  • Verwendung des Tabellenattributs MEMOPTIMIZE FOR READ im ALTER/CREATE TABLE Kommando
  • Befüllen des Rowstores mit Daten über DBMS_MEMOPTIMIZE.POPULATE

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.

  • Die Tabelle darf nicht komprimiert sein.
  • Die Tabelle muss einen Primary Key haben.
  • Die Spalte darf nicht über "IDENTITY" definiert sein.
  • Beim CREATE TABLE kann nicht die Default Segment Einstellung "WITH DEFERRED STORAGE" verwendet werden.
  • Es gibt keine Unterstützung für Reference Partitioned Tables.

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

 
Verfügbarkeit und Download

 

Zurück zur Community-Seite
 

Visit the Oracle Blog

 

Contact Us

Oracle

Integrated Cloud Applications & Platform Services