Le tabelle temporanee sono un concetto utile presente nella maggior parte degli SGBD, anche se spesso funzionano in modo diverso.
Questo blog descrive le caratteristiche tecniche di questo tipo di tabelle nei database PostgreSQL (versione 11) o Oracle (versione 12c) con alcuni esempi specifici. Sebbene lo scopo di queste tabelle possa essere lo stesso per tutti gli SGBD, le loro specifiche o il modo di implementazione e manipolazione sono completamente diversi.
Questa funzione può essere utilizzata sia dagli sviluppatori che dagli amministratori di database per archiviare risultati intermedi che saranno necessari per ulteriori elaborazioni al fine di fornire buone metriche delle prestazioni.
Tabelle temporanee in PostgreSQL
In PostgreSQL questi oggetti sono validi solo per la sessione corrente:vengono creati, utilizzati e rilasciati lungo la stessa sessione:la struttura della tabella e i dati gestiti sono visibili solo per la sessione corrente, quindi le altre sessioni non hanno accesso a le tabelle temporanee create sulle altre sessioni.
Di seguito viene mostrato un semplice esempio per creare una tabella temporanea:
CREATE TEMPORARY TABLE tt_customer
(
customer_id INTEGER
)
ON COMMIT DELETE ROWS;
Le tabelle temporanee vengono create in uno schema temporaneo:pg_temp_nn ed è possibile creare indici su queste tabelle:
creation index tt_cusomer_idx_1 on tt_customer(customer_id)
Poiché potrebbero essere eliminate anche le righe di dati di queste tabelle, è possibile liberare la memoria occupata tramite l'esecuzione di vaccum comando:
VACUUM VERBOSE tt_customer
L'analisi comando può essere eseguito anche sulle tabelle temporanee per raccogliere le statistiche:
ANALYZE VERBOSE tt_customer;
Entrambi i comandi possono essere eseguiti per questo tipo di tabella come comando SQL, tuttavia, autovaccum il demone che li esegue non agisce sulle tabelle temporanee.
Un altro punto importante da considerare è relativo alle tabelle permanenti e temporanee con lo stesso nome:una volta che accade, viene presa in considerazione solo la tabella permanente quando viene chiamata con il suo schema come prefisso.
web_db=# BEGIN TRANSACTION;
BEGIN
web_db=# SELECT COUNT(*) FROM customers;
count
---------
1030056
(1 row)
web_db=# CREATE TEMPORARY TABLE customers(
web_db(# id INTEGER
web_db(# )
web_db-# ON COMMIT PRESERVE ROWS;
CREATE TABLE
web_db=# INSERT INTO customers(id) VALUES(1023);
INSERT 0 1
web_db=# SELECT COUNT(*) FROM customers;
count
-------
1
(1 row)
web_db=# \dt *customers*
List of relations
Schema | Name | Type | Owner
-----------+----------------------+-------+----------
pg_temp_5 | customers | table | postgres
web_app | customers | table | postgres
web_app | customers_historical | table | postgres
(3 rows)
web_db=# DROP TABLE customers;
DROP TABLE
web_db=# \dt *customers*
List of relations
Schema | Name | Type | Owner
---------+----------------------+-------+----------
web_app | customers | table | postgres
web_app | customers_historical | table | postgres
(2 rows)
web_db=# SELECT COUNT(*) FROM web_app.customers;
count
---------
1030056
(1 row)
web_db=# SELECT COUNT(*) FROM customers;
count
---------
1030056
(1 row)
Dall'esempio precedente, mentre la tabella temporanea esiste, tutti i riferimenti ai clienti fa riferimento a questa tabella invece che a quella permanente.
Suggerimenti per gli sviluppatori per tabelle temporanee
Lo scopo di questo esempio è quello di assegnare un bonus ai clienti che non effettuano acquisti o non effettuano il login da più di un anno, quindi lo script dello sviluppatore utilizza invece le sottoquery nelle query come possibile soluzione (o l'utilizzo di CTE istruzione) può utilizzare tabelle temporanee (che di solito è più veloce dell'utilizzo di sottoquery):
web_db=# BEGIN TRANSACTION;
BEGIN
web_db=# CREATE TEMPORARY TABLE tt_customers(
web_db(# id INTEGER
web_db(# )
web_db-# ON COMMIT DELETE ROWS;
CREATE TABLE
web_db=# SELECT COUNT(*) FROM tt_customers;
count
-------
0
(1 row)
web_db=# INSERT INTO tt_customers(id)
web_db-# SELECT customer_id
web_db-# FROM web_app.orders
web_db-# WHERE order_dt <= NOW()-INTERVAL '6 MONTH';
INSERT 0 1030056
web_db=# SELECT COUNT(*) FROM tt_customers;
count
---------
1030056
(1 row)
web_db=# DELETE FROM tt_customers c
web_db-# WHERE EXISTS(SELECT 1
web_db(# FROM web_app.users u JOIN web_app.login l
web_db(# ON (l.user_id=u.user_id)
web_db(# WHERE u.customer_id=c.id
web_db(# AND l.login_dt > NOW()-INTERVAL '6 MONTH'
web_db(# );
DELETE 194637
web_db=# SELECT COUNT(*) FROM tt_customers;
count
--------
835419
(1 row)
web_db=# UPDATE web_app.customers as c SET BONUS=5
web_db-# FROM tt_customers t
web_db-# WHERE t.id = c.id;
UPDATE 835419
web_db=# SELECT COUNT(*) FROM tt_customers;
count
--------
835419
(1 row)
web_db=# COMMIT TRANSACTION;
COMMIT
web_db=# SELECT COUNT(*) FROM tt_customers;
count
-------
0
(1 row)
Suggerimenti DBA per tabelle temporanee
Un'attività tipica per gli amministratori di database consiste nell'eliminare le tabelle di grandi dimensioni che contengono dati che non sono più necessari. Questo deve essere completato molto rapidamente e succede spesso. L'approccio standard consiste nello spostare questi dati in una tabella cronologica in un altro schema o in un database a cui si accede meno spesso.
Quindi, per eseguire questo spostamento, a causa di problemi di prestazioni, la soluzione migliore potrebbe essere l'utilizzo di tabelle temporanee:
CREATE TEMPORARY TABLE tt_customer
(
customer_id INTEGER
)
ON COMMIT DROP;
In questo esempio, la tabella temporanea è stata creata con l'opzione DROP, quindi significa che verrà eliminata alla fine del blocco della transazione corrente.
Ecco alcune altre informazioni importanti sulle tabelle temporanee di PostgreSQL:
- Le tabelle temporanee vengono automaticamente eliminate al termine di una sessione o, come illustrato nell'esempio precedente, al termine della transazione corrente
- Le tabelle permanenti con lo stesso nome non sono visibili nella sessione corrente mentre esiste la tabella temporanea, a meno che non siano referenziate con nomi qualificati per lo schema
- Anche gli indici creati su una tabella temporanea sono automaticamente temporanei
- ON COMMIT conserva le righe è il comportamento predefinito
- Opzionalmente, GLOBAL o LOCAL possono essere scritti prima di TEMPORARY o TEMP. Questo attualmente non fa differenza in PostgreSQL ed è deprecato
- L'autovuoto daemon non può accedere a queste tabelle e quindi non può aspirare o analizzare tabelle temporanee, tuttavia, come mostrato in precedenza, i comandi autovacuum e analysis possono essere usati come comandi SQL.
Global Temporary Tables (GTT) in Oracle
Questo tipo di tabelle è conosciuto nel mondo Oracle come Global Temporary Table (o GTT). Questi oggetti sono persistenti nel database e possono essere riassunti dalle seguenti caratteristiche:
- La struttura è statica e visibile a tutti gli utenti, tuttavia il suo contenuto è visibile solo per la sessione corrente
- Può essere creato in uno schema specifico (per impostazione predefinita sarà di proprietà dell'utente che esegue il comando) e sono compilati nel tablespace TEMP
- Una volta creato nel database non può essere ricreato in ogni sessione, tuttavia i dati gestiti da una sessione non sono visibili per le altre sessioni
- È possibile la creazione di indici e la generazione di statistiche
- Poiché la struttura di queste tabelle è definita anche nel database non è possibile assegnare il suo nome ad una tabella permanente (in Oracle due oggetti non possono avere lo stesso nome anche di tipo diverso)
- Non genera troppi log di ripristino e l'overhead di annullamento è anche inferiore rispetto a una tabella permanente (solo per questi motivi l'utilizzo di GTT è più veloce) per qualsiasi versione precedente alla 12c. Dalla versione 12c esiste un concetto di annullamento temporaneo, che consente di scrivere l'annullamento per un GTT nel tablespace temporaneo, riducendo così annulla e ripristina.
Seguendo lo stesso esempio presentato in PostgreSQL, la creazione di un GTT è abbastanza simile:
CREATE GLOBAL TEMPORARY TABLE tt_customer
(
customer_id NUMBER
)
ON COMMIT DELETE ROWS;
È possibile anche la creazione di indici.
creation index tt_cusomer_idx_1 on tt_customer(customer_id)
Prima di Oracle 12c la generazione delle statistiche per una tabella temporanea globale aveva un comportamento in modo globale:le statistiche generate in una specifica sessione per uno specifico GTT erano visibili e utilizzate per le altre sessioni (solo le statistiche non i dati!), tuttavia, dalla versione 12c è possibile per ogni sessione generare le proprie statistiche.
Innanzitutto è necessario impostare la preferenza global_temp_table_stats alla sessione :
exec dbms_stats.set_table_prefs(USER,’TT_CUSTOMER’,’GLOBAL_TEMP_TABLE_STATS’,’SESSION’);
e poi la generazione delle statistiche:
exec dbms_stats.gather_table_stats(USER,’TT_CUSTOMER’);
La tabella temporanea globale esistente potrebbe essere verificata mediante l'esecuzione della seguente query:
select table_name from all_tables where temporary = 'Y';
Suggerimenti per gli sviluppatori per Global Temporary Tables (GTT)
Seguendo l'esempio sulla sezione PostgreSQL:per assegnare un bonus ai clienti che non effettuano acquisti o non effettuano il login da più di un anno, l'utilizzo delle tabelle temporanee globali in Oracle ha lo stesso obiettivo che in PostgreSQL:ottenere prestazioni migliori sia nel utilizzo della risorsa o nella velocità di esecuzione.
SQL> SELECT COUNT(*) FROM tt_customers;
COUNT(*)
----------
0
SQL>
SQL> INSERT INTO tt_customers(id)
2 SELECT customer_id
3 FROM orders
4 WHERE order_dt <= ADD_MONTHS(SYSDATE,-6);
1030056 rows created.
SQL>
SQL> SELECT COUNT(*) FROM tt_customers;
COUNT(*)
----------
1030056
SQL>
SQL> DELETE FROM tt_customers c
2 WHERE EXISTS(SELECT 1
3 FROM users u JOIN login l
4 ON (l.user_id=u.user_id)
5 WHERE u.customer_id=c.id
6 AND l.login_dt > ADD_MONTHS(SYSDATE,-6)
7 );
194637 rows deleted.
SQL>
SQL> SELECT COUNT(*) FROM tt_customers;
COUNT(*)
----------
835419
SQL>
SQL> UPDATE CUSTOMERS c SET BONUS=5
2 WHERE EXISTS(SELECT 1 FROM tt_customers tc WHERE tc.id=c.id);
835419 rows updated.
SQL>
SQL> SELECT COUNT(*) FROM tt_customers;
COUNT(*)
----------
835419
SQL>
SQL> COMMIT;
Commit complete.
SQL>
SQL> SELECT COUNT(*) FROM tt_customers;
COUNT(*)
----------
0
SQL>
Per impostazione predefinita in Oracle un blocco/istruzione SQL/PLSQL avvia implicitamente una transazione.
Suggerimenti DBA per Global Temporary Tables (GTT)
Come l'affermazione drop non esiste per le tabelle temporanee globali il comando per creare la tabella è lo stesso del precedente:
CREATE GLOBAL TEMPORARY TABLE tt_customer
(
customer_id NUMBER
)
ON COMMIT DELETE ROWS;
Lo snippet di codice equivalente in Oracle per eliminare il cliente tabella è la seguente:
SQL> INSERT INTO tt_customers(id)
2 SELECT l.user_id
3 FROM users u JOIN login l
4 ON (l.user_id=u.user_id)
5 WHERE l.login_dt < ADD_MONTHS(SYSDATE,-12);
194637 rows created.
SQL>
SQL> INSERT INTO tt_customers(id)
2 SELECT user_id
3 FROM web_deactive;
2143 rows created.
SQL>
SQL> INSERT INTO tt_customers(id)
2 SELECT user_id
3 FROM web_black_list;
4234 rows created.
SQL>
SQL> INSERT INTO customers_historical(id,name)
2 SELECT c.id,c.name
3 FROM customers c,
4 tt_customers tc
5 WHERE tc.id = c.id;
201014 rows created.
SQL>
SQL> DELETE FROM customers c
2 WHERE EXISTS (SELECT 1 FROM tt_customers tc WHERE tc.id = c.id );
201014 rows deleted.
La libreria pg_global_temp_tables
Come accennato in precedenza, le tabelle temporanee in PostgreSQL non possono essere richiamate utilizzando la notazione schema.table , quindi la libreria pg_global_temp_tables (ci sono alcune librerie simili disponibili su github) è una soluzione molto utile da utilizzare nelle migrazioni di database da Oracle a PostgreSQL.
Per mantenere la notazione Oracle schema.temporary_table nelle query o nelle stored procedure:
SELECT c.id,c.nam
FROM web_app.tt_customers tc,
Web_app.customers c
WHERE c.id = tc.id
Consente di mantenere le tabelle temporanee sul codice con la notazione dello schema.
Fondamentalmente, consiste in una vista:web_app.tt_customers creato sotto lo schema su cui dovrebbe avere la tabella temporanea e questa vista interrogherà la tabella temporanea tt_customers tramite una funzione chiamata web_app.select_tt_customers :
CREATE OR REPLACE VIEW WEB_APP.TT_CUSTOMERS AS
SELECT * FROM WEB_APP.SELECT_TT_CUSTOMERS();
Questa funzione restituisce il contenuto della tabella temporanea:
CREATE OR REPLACE FUNCTION WEB_APP.SELECT_TT_CUSTOMERS() RETURNS TABLE(ID INR, NAME VARCHAR) AS $$
BEGIN
CREATE TEMPORARY TABLE IF NOT EXISTS TT_CUSTOMERS(ID INT, NAME) ON COMMIT DROP;
RETURN QUERY SELECT * FROM TT_CUSTOMERS;
END;
$$ LANGUAGE PLPGSQL;
Riepilogo
Le tabelle temporanee vengono utilizzate essenzialmente per memorizzare risultati intermedi e quindi evitare calcoli complessi e pesanti,
Successivamente vengono elencate alcune caratteristiche delle tabelle temporanee in PostgreSQL o Oracle:
- Può essere utilizzato a vista
- Può usare il comando TRUNCATE
- Non può essere partizionato
- Il vincolo di chiave esterna sulle tabelle temporanee non è consentito
- Questo tipo di tabelle è un'alternativa alle CTE (Common Table Expressions) note anche per i professionisti Oracle come clausola WITH
- In termini di sicurezza e privacy, queste tabelle sono una risorsa preziosa perché i dati sono visibili solo per una sessione corrente
- Le tabelle temporanee vengono automaticamente eliminate (in PostgreSQL) o eliminate (in Oracle) al termine della sessione/transazione.
Per le tabelle temporanee in PostgreSQL è consigliabile non utilizzare lo stesso nome di una tabella permanente in una tabella temporanea. Dal lato Oracle è buona norma la generazione di statistiche per le sessioni che includono notevoli volumi di dati in GTT in modo da costringere il Cost-Based Optimizer (CBO) a scegliere il miglior piano per le query che utilizzano questo tipo di tabelle .