Управление схемами

(Schema Management, by Arup Nanda )

Аруп Нанда,
Член-директор коллегии Oracle ACE

 

Источник: сайт корпорации Oracle,
серия статей «Oracle Database 11g: The Top New Features for DBAs and Developers»
(«Oracle Database 11g: Новые возможности для администраторов и разработчиков»), статья 4
http://www.oracle.com/technetwork/articles/sql/11g-schemamanagement-089869.html

При использовании новой функциональности управление объектами базы данных производится более эффективно, многие общие операции выполняются невероятно просто и быстро.

Oracle Database 11g включает в себя множество функций, которые не только делают работу простой, но в иных случаях некоторые трудоемкие и времязатратные процессы сокращаются практически до линейных операций. В этой статье рассказывается о некоторых из этих функций.

Опция ожидания в DDL (DDL Wait Option)

Джилл, администратор базы данных (АБД) в компании Acme Retailers, пытается добавить столбец TAX_CODE в таблицу SALES. Это довольно рутинная процедура, выполняемая  следующим SQL-предложением:

SQL> alter table sales add (tax_code varchar2(10));

Но вместо того, чтобы получить что-то вроде "Table altered" ("Таблица изменена"), она получает:

 
alter table sales add (tax_code varchar2(10))
            *
ERROR at line 1:
ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired

(Ошибка в строке 1: 
ORA-00054: ресурс занят. В запросе  указана  опция NOWAIT или истек тайм-аут.) 

Сообщение об ошибке говорит само за себя: таблица сейчас, вероятно,   используется какой-то транзакцией, так что получить эксклюзивную блокировку на таблицу представляется почти невозможным. Конечно, строки таблицы блокированы не навсегда. Когда сессии завершатся, блокировки с её строк снимутся, но до этого ещё очень далеко. Тем временем другие сессии могут начать обновлять какие-либо другие строки этой же таблицы, и тем самым вероятность получить эксклюзивную блокировку таблицы практически исчезает. В обычной бизнес-среде периодически открывается временное окно для эксклюзивной блокировки таблицы, но АБД может не суметь выполнить команду ALTER именно в это время.  

Конечно, Джилл просто может снова и снова вводить эту команду, пока не получает эксклюзивную блокировку или сходит с ума, что скорее всего наступит раньше. В Oracle Database 11g для Джилл есть лучшая альтернатива: опция ожидания в DDL (DDL Wait option). Джилл вводит:

 
 SQL> alter session set ddl_lock_timeout = 10;
 
Session altered.

Теперь, если в этой сессии DDL-предложение не получит эксклюзивную блокировку, оно не выдаст ошибку. Вместо этого DDL-предложение ждёт 10 секунд, в течение которых оно  постоянно пытается повторно выполнить DDL — операцию до её успешного завершения или до истечения заданного времени, — что наступит раньше. Если же Джилл введёт:

SQL> alter table sales add (tax_code varchar2(10));

предложение зависнет, но без ошибки. Тем самым вместо того, чтобы несколько раз пытаться врезаться в неуловимый  интервал времени, когда доступна эксклюзивная блокировка, Джилл переадресует эти повторные попытки Oracle Database 11g, что похоже на программирование повторения  телефонных вызовов на занятый номер.

Теперь Джилл настолько любит эту функцию, что она рекомендует её всем другим АБД. Все, кто сталкивается с этой проблемой занятости системы при изменении таблицы, считают эту новую функцию очень полезной. Более того, Джилл задается вопросом, можно ли задать такое поведение по умолчанию, чтобы не нужно было выдавать каждый раз предложение ALTER SESSION?

Да, это возможно. Если вы введёте предложение

ALTER SYSTEM SET DDL_LOCK_TIMEOUT = 10 ;

то все сессии автоматически будут останавливаться на этот срок в течение DDL-операций. Как и любой другое предложение ALTER SYSTEM оно может быть перекрыто предложением ALTER SESSION

Добавление столбцов со значением по умолчанию
(Adding Columns with a Default Value)

Осчастливленная уже одной этой функцией Джилл обдумывает другую проблему, несколько связанной с первой. Она хочет добавить столбец TAX_CODE, но чтобы он не был NULL. Очевидно, когда добавляется ненулевой столбец в непустую таблицу, должно также указать значение по умолчанию 'XX'. Поэтому Джилл пишет следующее SQL-предложение:

alter table sales add tax_code varchar2(20) default 'XX' not null;

Но здесь она стопорится. Таблица SALES огромна, в ней около 400 миллионов строк. Джилл знает, что когда она введет это предложение, Oracle правильно добавит столбец, но обновит его значение 'XX' во всех строках перед возвращением управления. Обновление 400 миллионов строк не только займет очень много времени, но также потребует много операций с сегментами отката, породит большое количество записей журнала (redo log), а также потребует очень больших   накладных расходов. Поэтому Джилл должна запросить остановку работы на "тихий период", чтобы сделать эти изменения. Но может быть Oracle Database 11g предложит лучший вариант?

Разумеется. SQL-предложение, показанное выше, не станет производить обновления всех записей таблицы. Это, скажем, не проблема для новых записей, в которых значение столбца автоматически устанавливается в 'XX', но если пользователь выбирает этот столбец из уже существующей записи, то выберется NULL, не так ли?

На самом деле не так. Когда пользователь выбирает столбец из существующей записи, Oracle получает значение по умолчанию из словаря данных и возвращает его пользователю. Тем самым убиваются два зайца: новый столбец можно определить как непустой и со значением по умолчанию, и до поры нет никаких расходов по части генерации записей  redo и undo.

Виртуальные столбцы (Virtual Columns)

База данных компании Acme содержит таблицу SALES, которую вы видели ранее. Таблица имеет следующую структуру:

SALES_ID NUMBER
CUST_ID NUMBER
SALES_AMTNUMBER

Некоторые пользователи хотят, чтобы был добавлен столбец  SALE_CATEGORY, который определяет тип продажи: LOW, MEDIUM, HIGH или ULTRA в зависимости от количества в запросе продаж и клиентов. Этот столбец  поможет им выявить записи для принятия соответствующих действий и выбора маршрутов для конкретных сотрудников. Логика значений в этом столбце такова:  

Если sale_amt более чем: И sale_amt меньше или равна: Тогда значение sale_category:
01000 LOW
10001 100000 MEDIUM
100001 1000000 HIGH
1000001 Неограниченный ULTRA

Хотя этот столбец  является одним из важнейших требований бизнеса, команда разработчиков не хочет изменить код для включения необходимой логики. Конечно, можно добавить новый столбец в таблицу sale_category и написать триггер для заполнения столбца, используя правила, показанные выше, — довольно тривиальная задача. Но возникают проблемы с производительностью из-за переключения контекста из и в код триггера.

В Oracle Database 11g не нужно писать ни строчки кода в каком-либо триггере. Все, что нужно сделать, так это добавить виртуальный столбец. Виртуальные столбцы обеспечивают гибкость. Добавляемые столбцы передают смысл бизнеса без усложнения и снижения производительности.

Вот как нужно создать такую таблицу:

SQL> create table sales
  2  (
  3     sales_id      number,
  4     cust_id       number,
  5     sales_amt     number,
  6     sale_category varchar2(6)
  7     generated always as
  8     (
  9        case
 10           when sales_amt <= 10000 then 'LOW'
 11           when sales_amt > 10000 and sales_amt <= 100000 then 'MEDIUM'
 12           when sales_amt > 100000 and sales_amt <= 1000000 then 'HIGH'
 13           else 'ULTRA'
 14        end
 15      ) virtual
 16  );

Примечание к строкам 6-7: столбец определяется как "generated always as" ("всегда генерируется как"), ("всегда генерируется как"), то есть значения в столбце генерируются во время выполнения, а не хранятся как часть таблицы. Из этой фразы следует, что значение вычисляется в соответствующей фразе CASE. Далее   в строке 15 фраза "virtual" ("виртуальный") подтверждает, что это виртуальный столбец. Теперь вставим несколько записей:

SQL> insert into sales (sales_id, cust_id, sales_amt) values (1,1,100);
 1 row created.
 SQL> insert into sales (sales_id, cust_id, sales_amt) values (2,102,1500);
 1 row created.
 SQL>insert into sales (sales_id, cust_id, sales_amt) values (3,102,100000);
 1 row created.
 SQL> commit;
 Commit complete.
 SQL> select * from sales;
 SALES_ID  CUST_ID  SALES_AMT  SALE_C
---------- -------- ---------- ------
1          1        100        LOW
2          102      1500       LOW
3          102      100000     MEDIUM
 
3 rows selected.

Значения   всех виртуальных столбцов заполняются как обычно. Даже если этот столбец не хранится, к нему можно обратиться как к любому другому столбцу в таблице. Можно даже для него создать индекс.

SQL> create index in_sales_cat on sales (sale_category);
 
Index created.

В результате появится  индекс, базирующийся на функции (function-based index).

SQL> select index_type
  2  from user_indexes
  3  where index_name = 'IN_SALES_CAT';
 
INDEX_TYPE
---------------------------
FUNCTION-BASED NORMAL

SQL>  select column_expression
  2  from user_ind_expressions
  3  where index_name = 'IN_SALES_CAT';
 
COLUMN_EXPRESSION
--------------------------------------------------------------------------------
CASE  WHEN "SALES_AMT"<=10000 THEN 'LOW' WHEN ("SALES_AMT">10000 AND "SALES_AMT"
<=100000) THEN CASE  WHEN "CUST_ID"<101 THEN 'LOW' WHEN ("CUST_ID">=101 AND "CUS
T_ID"<=200) THEN 'MEDIUM' ELSE 'MEDIUM' END  WHEN ("SALES_AMT">100000 AND "SALES
_AMT"<=1000000) THEN CASE  WHEN "CUST_ID"<101 THEN 'MEDIUM' WHEN ("CUST_ID">=101
 AND "CUST_ID"<=200) THEN 'HIGH' ELSE 'ULTRA' END  ELSE 'ULTRA' END

По этому столбцу можно даже секционировать таблицу, как показано в статье Partitioning installment этой серии. Однако в этот столбец нельзя вводить значения. Если вы попытаетесь сделать это, то далеко не уедете:

insert into sales values (5,100,300,'HIGH','XX');
            *
ERROR at line 1: 

 ORA-54013: INSERT operation disallowed on virtual columns
(ORA-54013: операция INSERT не допустима для виртуальных столбцов)

Невидимые индексы (Invisible Indexes)

Часто ли вы задаетесь вопросом, действительно ли индекс полезен для пользовательских запросов? Он может быть выгоден для одного, но вреден для 10 других пользователей. Индексы, безусловно, негативно влияют на предложения INSERT, а также, возможно, на операции удаления (deletes) и обновления (updates) в зависимости от условия WHERE, которое включает в себя столбец в индексе.

В связи с этим актуален вопрос, если индекс используется на всех запросов, то, что происходит при исполнении запросов, если удален этот   индекс? Конечно, вы сами можете удалить этот индекс и увидеть влияние на запросы, но это легче сказать, чем сделать. Что же делать, если индекс сделан именно для того, чтобы   на деле помочь выполнению запросов? Вы должны восстановить использование индекса, но для этого его нужно воссоздать. А пока он полностью не воссоздан, никто не может его использовать. Пересоздание   индекса также дорогостоящий процесс, он занимает много ресурсов базы данных, которым можно найти лучшее применение.

А вот если бы был вариант, чтобы  индекс был неиспользуемым для некоторых запросов, но  не затрагивая все другие  запросы? Известная команда  ALTER INDEX ... UNUSABLE в  данном  случае (до Oracle   Database 11g) не вариант, поскольку она обязательно воздействует на все DML-операции на этой таблице. Но теперь есть именно требуемое решение с помощью невидимых индексов. Проще говоря, индекс  можно сделать "невидимым" для оптимизатора, и поэтому запрос не будет его использовать. Если же запрос хочет использовать индекс, он должен явно это   указать, используя хинт.

Приведу пример. Допустим, есть таблица RES, для которой создан индекс, как показано ниже:

 SQL> create index in_res_guest on res (guest_id);

After analyzing this table and index, if you 

SQL> select * from res where guest_id = 101;

этот индекс можно найти, используя:

Execution Plan
----------------------------------------------------------
Plan hash value: 1519600902
 
--------------------------------------------------------------------------------------------
| Id  | Operation                   | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |              |     1 |    28 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| RES          |     1 |    28 |     3   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | IN_RES_GUEST |     1 |       |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - access("GUEST_ID"=101)

Теперь сделаем этот индекс невидимым (invisible):

SQL> alter index in_res_guest invisible;
 
Index altered.

The following now shows: 

SQL> select * from res where guest_id = 101
  2  /
 
Execution Plan
----------------------------------------------------------
Plan hash value: 3824022422
 
--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |    28 |   140   (2)| 00:00:02 |
|*  1 |  TABLE ACCESS FULL| RES  |     1 |    28 |   140   (2)| 00:00:02 |
--------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter("GUEST_ID"=101)

показывает, что индекс не используется. А для того, чтобы оптимизатор снова использовал индекс, нужно его явно назвать в хинте-подсказке:

SQL> select /*+ INDEX (res IN_RES_GUEST) */ res_id from res where guest_id = 101;

--------------------------------------------------------------------------------------------
| Id  | Operation                   | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |              |     1 |    28 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| RES          |     1 |    28 |     3   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | IN_RES_GUEST |     1 |       |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------

Как же здорово! Индекс снова используется оптимизатором.

Кроме этого, чтобы использовать невидимые индексы на уровне сессии, можно установить параметр:

SQL> alter session set optimizer_use_invisible_indexes = true;

Эта возможность очень полезна, когда вы не можете изменить код, например, в сторонних приложениях. При создании индекса можно в конец добавить фразу INVISIBLE, чтобы построить индекс, как невидимый для оптимизатора. Вы также можете видеть текущее значение индекса с помощью представления словаря данных  USER_INDEXES.

SQL> select visibility
  2  from user_indexes
  3 where index_name = 'IN_RES_GUEST';

VISIBILITY
---------
INVISIBLE

Обратите внимание, что при перестроении этого индекса  он снова станет видимым. И вы должны снова явно сделать его невидимым.   

Итак, что именно делает этот индекс невидимым? Ну, он же не невидимый для пользователя. Он   невидим только для оптимизатора. Регулярные операции с базами данных, такие как вставки, обновления и удаления строк будут продолжать обновлять индекс. Помните, что при использовании   невидимых индексов нет прироста производительности за счет индекса, хотя вы оплачиваете стоимость   DML-операций.

Таблицы только_для_чтения (Read-Only Tables)

Робин, разработчик хранилища данных Acme, озабочен классическими проблемами. Как часть ETL-процессов несколько таблиц обновляются с разной периодичностью. По бизнес-правилам при обновлении таблицы   открыты для пользователей, даже если пользователи не  имеют права  их изменять. Таким образом, отмена DML-привилегий на эти таблицы для таких -  пользователей не вариант.

Поэтому Робину нужно средство, которое действует как переключатель, включая и выключая возможность обновления таблицы. Реализация такой тривиально звучащей операции на самом деле довольно сложно. Что может сделать Робин?

Одним из вариантов является создание триггера на таблицу, который вызовет исключение при операциях INSERT, DELETE    и UPDATE. Выполнение триггера, вызывающего  переключение контекста, не хорошо для производительности. Другой вариант заключается в создании виртуальной частной базы данных (Virtual Private Database (VPD)), политика которой всегда содержит ложную строку, например, "1 = 2". Когда табличная VPD-политика использует эту функцию, она возвращает FALSE, и DML-предложение  не выполнятся. Это может быть более производительно, чем использование триггера, но определенно менее желательно, так как пользователи увидят сообщение об ошибке, что "функция политики вернула ошибку".

Однако в Oracle Database 11g, есть гораздо лучший способ достижения этой цели. Все, что нужно - это сделать таблицу только для чтения, как показано ниже:

SQL> alter table TRANS read only;

Table altered .

Теперь, когда пользователь пытается выдать DML, как показано ниже:

SQL> delete trans; 

Oracle Database 11g сразу выдает ошибку:  

 delete trans
       *
ERROR at line 1:
ORA-12081: update operation not allowed on table "SCOTT"."TRANS"

[ORA-12081: операция udate не разрешена на таблице "SCOTT"."TRANS"]

Сообщение об ошибке не отражают операции по букве, но передает сообщение, как и предполагалось, без накладных расходов на курсор или VPD-политику.

Когда понадобится восстановить возможность обновления таблицы, вам нужно сделать её для read/write (чтение/запись), как показано ниже:

SQL> alter table trans read write;

Table altered .

Теперь с DML-операциями  не будет никаких проблем:

SQL> update trans set amt = 1 where trans_id = 1;

1 row updated 

В то время как таблица находится в режиме read-only, запрещены только DML-операции, но можно выполнять все DDL-операции (создание индексов, управление секциями и так далее). Таким образом, это очень полезная функция для обслуживания таблиц. Вы можете сделать таблицу read-only, выполнить необходимые DDL-операции, а затем снова перевести её в статус read/write.

Чтобы посмотреть состояние таблицы, обратим внимание на столбец read_only в представлении dba_tables словаря данных.

SQL> select read_only from user_tables where table_name = 'TRANS';
 
REA
---
NO

Мелкодисперсное отслеживание зависимостей
(Fine-Grained Dependency Tracking)

Эту возможность лучше всего объяснить на примере. Рассмотрим таблицу TRANS, созданную как:

create table trans
(
    trans_id        number(10),
    trans_amt       number(12,2),
    store_id        number(2),
    trans_type      varchar2(1)
)

Пользователи не должны напрямую получать данные из этой таблицы, они получают их через представление VW_TRANS, созданное как показано ниже:

create or replace view vw_trans
as
select trans_id, trans_amt  
from trans;

Теперь представление VW_TRANS зависит от таблицы TRANS. Вы можете проверить эту зависимость при помощи следующего запроса:

select d.referenced_name, o.status 
from user_dependencies d, user_objects o
where d.name = o.object_name
and d.name = 'VW_TRANS'
/ 

REFERENCED_NAME    STATUS
----------------- -------
TRANS              VALID 

Как показано, статус представления VW_TRANS - VALID. Далее изменим базовую таблицу, например, добавим столбец:

 alter table trans add (trans_date date);

Поскольку представление зависит от таблицы, которая была изменена, то это   представление в Oracle Database 10g и предыдущих релизах было бы   аннулировано. Можно проверить состояние зависимости и сейчас, используя показанный выше запрос:

REFERENCED_NAME                  STATUS
------------------------------- -------
TRANS                            INVALID


Статус показывает, что оно INVALID (недействительное). Принципиально ничего не изменилось, поскольку в общем случае представление стало  перманентно недействительным, но оно может быть легко подвергнуто повторной компиляции:  

alter view vw_trans compile;

Итак, почему это представление недействительно? Ответ прост: когда изменятся родительский объект, дочерние объекты автоматически подпадают под пристально внимание, потому что что-то в них,  возможно, потребуется изменить. Однако в этом случае изменением является добавление новых столбцов. Представление  не использует этот столбец, так почему оно должно быть признано недействительным?

В Oracle Database 11g это не так. Зависимость по-прежнему установлена на TRANS, конечно, но статус не INVALID – он по-прежнему VALID!

REFERENCED_NAME                  STATUS
------------------------------- -------
TRANS                            VALID


Поскольку представление не инвалидное, то и все зависимые от этого   представления объекты, например, другие представления,  пакеты и процедуры, также не инвалидны. Это свойство устраивает огромное количество приложений, что, в свою очередь, повышает общую доступность всего стека. Не  нужно стопорить программы, чтобы сделать некоторые изменения в базе данных.

Если был  изменен столбец, используемый, например, в представлении TRANS_AMT, представление было бы признано недействительным. Это желательно, так как столбцы альтер-таблицы могут повлиять на представление.

Но высокая доступность не замыкается  на одиночных представлениях и таблицах, вы нуждаетесь в них для других хранимых объектов, таких как процедуры и пакеты. Рассмотрим пакет показанный ниже:

create or replace package pkg_trans
is
        procedure upd_trans_amt
        (
                p_trans_id      trans.trans_id%type,
                p_trans_amt     trans.trans_amt%type
        );
end;
/

create or replace package body pkg_trans
is
        procedure upd_trans_amt
        (
                p_trans_id      trans.trans_id%type,
                p_trans_amt     trans.trans_amt%type
        ) is
        begin
                update trans
                set trans_amt = p_trans_amt
                where trans_id = p_trans_id;
        end;
end;
/

Теперь предположим, что вы хотите написать функцию, которая увеличивает объем сделки на определенный процент. Эта функция использует пакет pkg_trans.

create or replace function adjust
(
        p_trans_id      number,
        p_percentage    number
)
return boolean
is
        l_new_trans_amt number(12);
begin
        select trans_amt * (1 + p_percentage/100)
        into l_new_trans_amt
        from trans
        where trans_id = p_trans_id;
        pkg_trans.upd_trans_amt (
                p_trans_id,
                p_percentage
        );
        return TRUE;
exception
        when OTHERS then
                return FALSE;
end;
/

Если вы захотите проверить статус функции, она должна быть valid:

select status
from user_objects
where object_name = 'ADJUST'
/

STATUS
-------
VALID

Предположим, вы хотите изменить пакет pkg_trans путем добавления новых процедур для обновления столбца vendor_name. Вот новое определение пакета:

create or replace package pkg_trans
is
        procedure upd_trans_amt
        (
                p_trans_id      trans.trans_id%type,
                p_trans_amt     trans.trans_amt%type
        );
        procedure upd_vendor_name
        (
                p_trans_id      trans.trans_id%type,
                p_vendor_name   trans.vendor_name%type
        );
                
end;
/
create or replace package body pkg_trans
is
        procedure upd_trans_amt
        (
                p_trans_id      trans.trans_id%type,
                p_trans_amt     trans.trans_amt%type
        ) is
        begin
                update trans
                set trans_amt = p_trans_amt
                where trans_id = p_trans_id;
        end;
        procedure upd_vendor_name
        (
                p_trans_id      trans.trans_id%type,
                p_vendor_name   trans.vendor_name%type
        ) is
        begin
                update trans
                set vendor_name = p_vendor_name
                where trans_id = p_trans_id;
        end;
end;

После этого пакет перекомпилируется. Каким  будет статус функции ADJUST? В Oracle Database 10g и ниже, функция, будучи зависимой, считается недействительной, как показывает её в статус:

 
STATUS
------- 
INVALID

Её легко перекомпилировать alter function ... recompile;, но в Oracle Database 11g эта функция не будет считаться инвалидной (недействительной): STATUS ------- VALID

Это огромный шаг к понятию высокой доступности. Функция настройки не вызывает изменения части пакета pkg_trans, поэтому нет необходимости эту функцию признавать   инвалидной, и это справедливо не только в  Oracle Database 11g.

Но это не всегда так. Если пакет модифицирован таким образом, что новые суб-компоненты находится в его конце, как показано в приведенном выше примере, то зависимость хранимого кода  не является  инвалидной. Иначе будет, если суб-компонент добавляется в начало, как показано ниже:

create or replace package pkg_trans
is
        procedure upd_vendor_name ...
        procedure upd_trans_amt ...
end;

Хранимый зависимый код, ADJUST, является недействительным, так как это имеет место в Oracle Database 10g и ниже. Это происходит потому, что новая процедура, вставляемая перед существующими, меняет номера слотов в пакете, тем самым вызывая инвалидность. Когда процедура была вставлена после выхода из них, номера слотов не изменились, просто был добавлен  новый номер слота.

Вот некоторые общие рекомендации по снижению связанной зависимости инвалидизации.

Кроме того, если вы использовали механизм оперативного онлайн-переопределения (online redefinition), то ранее вы, возможно, видели, что переопределение (redefinition) делает некоторые зависимые объекты инвалидными. Этого больше нет в Oracle Database 11g. Теперь онлайн-переопределения не инвалидизирует объекты, если указанные в них столбцы одного и того же имени и типа. Если столбец был удален (dropped) во время REDEF, но процедура не используют этот столбец, то процедура не признается   инвалидной.

Примечание: В Oracle Database 11g Release 2 это описанное выше инвалидное поведение ведет себя по-другому. Чтобы продемонстрировать это, давайте создадим таблицу в базе данных Oracle Database 11g Release 1

create table trans
(
    trans_id        number(10),
    trans_amt       number(12,2),
    store_id        number(2),
    trans_type      varchar2(1)
)

Затем создадим триггер:

create trigger tr_trans
before insert on trans
for each row
declare
    l_store_id number(2);
begin
    l_store_id := :new.store_id;
end;
/

Проверим статус этого триггера:

SQL> select status from user_objects where object_name = 'TR_TRANS';

STATUS
-------
VALID

Изменим таблицу следующим  образом:

SQL> alter table trans add (col1 number);

Table altered.

Теперь проверим статус триггера:

SQL> select status from user_objects where object_name = 'TR_TRANS';

STATUS
-------
INVALID

В Oracle Database 11g Release 1 триггер был признан инвалидным, хотя это не имело ничего общего с модификацией таблицы. Однако в Release 2 он не будет считаться инвалидным, поскольку триггер не зависит от модификации таблицы. (Это   - новый столбец; существующий триггер никогда бы его не вызвал.)

Воссоздадим   этот сценарий в Oracle Database 11g Release 2 и проверим статус:

SQL> select status from user_objects where object_name = 'TR_TRANS';

STATUS
-------
VALID

1 row selected.

SQL> alter table trans add (col1 number);

Table altered.

SQL> select status from user_objects where object_name = 'TR_TRANS';

STATUS
-------
VALID

Триггер остается в силе. История будет другой, когда что-то изменится так, что это повлияет на триггер. Возьмем, к примеру,

SQL> alter table trans modify (store_id number(3));

Table altered.

SQL> select status from user_objects where object_name = 'TR_TRANS';

STATUS
------- 
INVALID

Таким образом,  во многих случаях изменения таблиц,   таких как добавление новых столбцов, независимые объекты будут признаны недействительными - создавая базы данных по-настоящему высокой доступности.

Внешние ключи по виртуальным столбцам (Только Release 2)
Foreign Keys on Virtual Columns (Release 2 Only)

В Oracle Database 11g Release 1 мы видели наличие двух новых очень важных и полезных функций. Одна из них виртуальные столбцы, описанные выше. Вторая -   секционирование на основе ссылочной целостности (partitioning based on referential integrity constraints)  - так называемое REF-секционирование (REF partitioning), которое позволяет секционировать дочерние таблицы-секции (partition child tables) как  и родительскую таблицу, даже если в ней (child table) нет столбца секционирования.

Эти две возможности предлагают различные преимущества: виртуальные столбцы позволяют управлять таблицей, не тратя ресурсы на хранение столбцов или на   изменения приложений, чтобы включить новые столбцы. А  REF-секционирование позволяет разделить таблицы на секции так, чтобы воспользоваться разграничениями в отношениях родитель-ребенок (parent-child relationships) без добавления этих столбцов в child-таблицах. А что, если вы захотите воспользоваться преимуществами обеих этих возможностей на одних и тех же наборах  таблиц? В Oracle Database 11g Release 2 это легко можно сделать.

Вот пример: таблица CUSTOMERS имеет два виртуальных столбца, CUST_ID, который также используется в качестве первичного ключа, и CATEGORY - столбец секционирования.   Таблица SALES является дочерней по отношению к таблице CUSTOMERS, с которой соединяются по CUST_ID. Давайте посмотрим этот код в действии.

create table customers
(
    cust_id     number(14)
        generated always as
        (
            DECODE(plan_id, 'MEDICAL',100, 'DENTAL',200, 'HOSPITAL ONLY',300, 999)
                || ssn ||
            DECODE(member_type, 'SELF','01', 'SPOUSE','02', 'CHILD','03', '99')
        ) virtual,
    cust_name   varchar2(20),
    ssn         varchar(9),
    plan_id     varchar2(15),
    member_type varchar2(10),
    category    varchar2(1)
        generated always as
        (case
            when member_type = 'SELF' then
                 case when plan_id = 'MEDICAL' then 'A' else 'B' end
            when member_type = 'SPOUSE' then
                 case when plan_id = 'MEDICAL' then 'B' else 'C' end
            when member_type = 'CHILD' then 'C' else 'X'
         end) virtual,
        constraint pk_customers primary key (cust_id)
)
partition by list (category)
(
        partition A values ('A'),
        partition B values ('B'),
        partition C values ('C'),
        partition DEF values (default)
 
)
/

Давайте вставим несколько строк, заботясь лишь о том, чтобы не назначить определенное значение для виртуальных столбцов. Мы хотим, чтобы были созданы виртуальные столбцы.

insert into insert into customers (cust_name, ssn, plan_id, member_type) values ('Jill','123456789','MEDICAL','SELF')
/  
insert into customers (cust_name, ssn, plan_id, member_type) values 
  ('John','123456789','MEDICAL','SPOUSE')
/
insert into customers (cust_name, ssn, plan_id, member_type) values
  ('Charlie','123456789','MEDICAL','CHILD')
/

Будут ли виртуальные столбцы правильно возвращать данные? Это мы сможем проверить, выбрав строки из таблицы:

 
select * from customers;
 
       CUST_ID CUST_NAME            SSN       PLAN_ID         MEMBER_TYP C
-------------- -------------------- --------- --------------- ---------- -
10012345678901 Jill                 123456789 MEDICAL         SELF       A
10012345678902 John                 123456789 MEDICAL         SPOUSE     B
10012345678903 Charlie              123456789 MEDICAL         CHILD      C

Теперь, когда родительская таблица готова, давайте создадим дочернюю таблицу:

create table sales
(
   sales_id    number primary key,
   cust_id     number not null,
   sales_amt   number,
   constraint  fk_sales_01
   foreign key (cust_id)
      references customers
)
partition by reference (fk_sales_01)
/

В 11g Release 1 этот код закончился бы ошибкой

ERROR at line 6:
ORA-14663: reference partitioning parent key is not supported
[ORA-14663: в опции reference partitioning родительский ключ не поддерживается.]

В 11g Release 2 эта операция возможна, и предложение создаст дочернюю таблицу. Применяя  эти возможности, можно использовать всю мощь двух довольно полезные функции в Oracle для построения  всё лучших моделей данных.

IPv6 Форматирование в JDBC (Release 2 Only)
(IPv6 Formatting in JDBC (Release 2 Only)

За   последние несколько лет IP-адресация подверглась капитальным изменениям. Традиционный способ адресования – это набор из четырех чисел, разделенных точками, например 192.168.1.100. Эта схема, называемая IPv4, является схемой 32-разрядной адресации, которая допускает сравнительно небольшое множество IP-адресов. При взрывоподобном росте спроса на IP-адреса для не только сайтов, но таких устройств, как IP-совместимые телефоны и PDA (personal digital assistant - персональный цифровой помощник), эта схема исчерпает свои IP-адреса в течение короткого времени. Для решения проблемы нового поколения IP-адресации была введена так называемая схема IPv6. Это 128-битная система, способная поддерживать гораздо большее множество адресов.

В Oracle Database 11g Release 2 можно сразу использовать схему IPv6. Приведем простой пример команды (в командной строки Linux), чтобы узнать адреса IPv6.

# /sbin/ifconfig -a
   eth0      Link encap:Ethernet  HWaddr 00:19:21:BB:9A:A5  
             inet addr:10.14.104.253  Bcast:10.14.107.255  Mask:255.255.252.0
             inet6 addr: fe80::219:21ff:febb:9aa5/64 Scope:Link
             UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
…  выход усечен …

Обратите внимание на традиционную  IP -адресацию (как показано в заголовке “inet addr ”): 10.14.104.253. Вы можете использовать схему адресации EZNAMES для подключения к базе данных под названием D112D1, которая по умолчанию работает через порт 1521:  

SQL> connect arup/arup@10.14.104.253/D112D1
   Connected.

Обратите внимание на выход команды Ifconfig. В дополнение к IPv4 вы можете увидеть схему адресации IPv6 (показан в столбце с заголовком "inet6 адрес"): fe80 :: 219:21 FF: febb: 9aa5. Вы можете использовать этот адрес вместо адреса IPv4. Вы должны заключить IPv6 в квадратные скобки.

SQL> connect arup/arup@[fe80::219:21ff:febb:9aa5]/D112D1
   Connected.

Поддержка IPv6 не ограничивается SQL*Plus. Вы можете, как показано ниже, использовать IPv6 также в JDBC:

jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)
   (HOST=[fe80::219:21ff:febb:9aa5]) (PORT=1526))
   (CONNECT_DATA=(SERVICE_NAME=D112D1)))

Конечно, можно использовать и IPv6, и IPv4 одновременно. Убедитесь только в том, что IPv6-адрес помещен в квадратных скобках.

Сегментов меньше, чем Объектов (только Release 2)
Segment-less Objects (Release 2 Only)

Рассмотрим ситуацию, когда сторонние приложения или даже ваше собственное приложение развертывается на нескольких тысячах таблиц. Каждая таблица имеет, по крайней мере, один сегмент, и даже если все они пусты, каждый сегмент занимают, по крайней мере, один экстент. В  каждый момент времени многие из этих таблиц могут быть пустыми, а могут   содержать записи. Поэтому не имеет смысла предварительно выделить всё пространство сразу же. Эта ситуация раздувает общий объем базы данных и увеличивает время установки приложения. Можно   отложить создание таблиц, но развертывание зависимых объектов, таких как процедуры и представления не позволяет это сделать без ошибок.

В 11g Release 2 есть довольно элегантное решение. В этом релизе сегменты не создаются по умолчанию при создании таблицы, а [физически создаются только тогда] когда в таблицы вставляются первые данные. Давайте посмотрим это на примере:

SQL> create table test (col1 number);
Table created.
SQL> select bytes from user_segments where segment_name = 'TEST';
no rows selected

Не существует сегмента для вновь созданной таблицы. Теперь вставим в таблицу строку:

SQL> insert into test values (1);
1 row created.
SQL> select bytes from user_segments where segment_name = 'TEST';
     BYTES
   ----------
     65536

Сегмент создается с начальным экстентом (initial extent). Это   необратимый процесс. Экстент сохраняется, даже если происходит откат.

SQL> roll
   Rollback complete.
   SQL> /
     BYTES
   ----------
     65536

Но эту возможность не обязательно заявлять по умолчанию. Например, вы хотите, чтобы сегмент создавался при создании таблицы. Параметр deferred_segment_creation управляет такой возможностью. Для создания сегментов при создании таблицы надо установить этот параметр в значение FALSE. Им можно управлять даже на уровне сеанса:

SQL> alter session set deferred_segment_creation = false;
Session altered.
SQL> create table test1 (col1 number);
Table created.
SQL> select bytes from user_segments where segment_name = 'TEST1';
   
   BYTES
   ----------
   65536

После создания сегмента он сохраняется в базе. Если вы опустошаете (truncate) таблицу, сегмент не удаляется. Обратите внимание, что эта возможность не применима к LOB-сегментам, которые создаются независимо, даже если не создается таблица сегмента.

SQL> create table testlob (col1 clob);
Table created.
SQL> select segment_name from user_lobs where table_name = 'TESTLOB';

   SEGMENT_NAME
   ------------------------------
   SYS_LOB0000079350C00001$$

Однако если посмотреть на таблицу сегментов:

SQL> select bytes from user_segments where segment_name = 'TESTLOB';

no rows selected 

Вы видите, что сегмент создан не был.

В Release 1 отложенное создание сегментов работало только для несекционированных объектов. Ограничение для секционированных таблиц было снято в Release 2, так что теперь эта функциональность применяется и для секционированных объектов.

Надо сказать, что по-прежнему существует ряд ограничений для различных типов объектов. Они подробно  описаны в справочном руководстве языка SQL.

Неиспользуемые индексы не требуют пространства (Только Release 2)
Unusable Indexes Do Not Consume Space (Release 2 Only)

Теперь, понимая преимущества отложенного создания сегмента, можно понадеяться на универсальность этого качества. Однако, после обращения к документации,   становится очевидным, что нет никакого способа сделать отложенное создание сегментов для индексов.

Этому существует идеальное объяснение: как класс вторичных структур – таблица всегда  создается раньше индекса – индексы просто следуют атрибутам таблицы. Если вы создаете пустую таблицу с отложенным созданием сегмента, для индекса также будет иметь место отложенное создание сегмента. Вы вставляете в таблицу первую запись, и вуаля, вы получите сегмент как для таблицы, так  и сегмент(ы) индекса(ов).

Рассмотрим следующий пример, где мы создадим индекс на таблицу с данными (или когда отложенное  создание сегмента была отключено).

SQL> create index in_test on test (col1);
Index created.
SQL> select bytes from user_segments where segment_name = 'IN_TEST';
   
   BYTES
   ----------
   65536

Сегмент создан. Но какой смысл хранения этого блока данных в базе данных? Вы никаким образом не можете получить к нему доступ, вы не можете использовать его для восстановления или чего-либо другого, кроме как занимая пространство,  - он бесполезен.

В Oracle Database 11g Release 2 существует неоценённая здесь возможность (и использует отложенное создание сегмента под одеялом). Но в этом выпуске, если вы делаете индекс unusable (непригодным для использования), исчезает соответствующая бесполезность сегмента:

SQL> alter index in_test unusable;
Index altered.
SQL> select bytes from user_segments where segment_name = 'IN_TEST';
no rows selected

При перестроении индекса (чтобы якобы начать использовать его), сегмент возникает:

SQL> alter index in_test rebuild;
Index altered.
SQL> select bytes from user_segments where segment_name = 'IN_TEST';
   
   BYTES
   ----------
   5536

Эта возможность особенно полезна при секционировании, когда можно для экономии места выборочно сделать индексы несуществующими. Давайте рассмотрим пример - таблица SALES   из схемы SH.

SQL> select partition_name, high_value
       2  from user_tab_partitions
       3  where table_name = 'SALES';
   PARTITION_NAME  HIGH_VALUE
   --------------  --------------------------------------------------------------------------------
   SALES_1995      TO_DATE(' 1996-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_1996      TO_DATE(' 1997-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_H1_1997   TO_DATE(' 1997-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_H2_1997   TO_DATE(' 1998-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q1_1998   TO_DATE(' 1998-04-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q2_1998   TO_DATE(' 1998-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q3_1998   TO_DATE(' 1998-10-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q4_1998   TO_DATE(' 1999-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q1_1999   TO_DATE(' 1999-04-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q2_1999   TO_DATE(' 1999-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q3_1999   TO_DATE(' 1999-10-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q4_1999   TO_DATE(' 2000-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q1_2000   TO_DATE(' 2000-04-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q2_2000   TO_DATE(' 2000-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q3_2000   TO_DATE(' 2000-10-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q4_2000   TO_DATE(' 2001-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q1_2001   TO_DATE(' 2001-04-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q2_2001   TO_DATE(' 2001-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q3_2001   TO_DATE(' 2001-10-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q4_2001   TO_DATE(' 2002-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q1_2002   TO_DATE(' 2002-04-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q2_2002   TO_DATE(' 2002-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q3_2002   TO_DATE(' 2002-10-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q4_2002   TO_DATE(' 2003-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q1_2003   TO_DATE(' 2003-04-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q2_2003   TO_DATE(' 2003-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q3_2003   TO_DATE(' 2003-10-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
   SALES_Q4_2003   TO_DATE(' 2004-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA

Давайте проиндексируем  одинаковые секции таблицы:

SQL> select index_name, partitioning_type
     2  from user_part_indexes
     3  where table_name = 'SALES';

INDEX_NAME         PARTITIONING_TYPE
-----------------  -----------------
SALES_PROD_BIX     RANGE
SALES_CUST_BIX     RANGE
SALES_TIME_BIX     RANGE
SALES_CHANNEL_BIX  RANGE
SALES_PROMO_BIX    RANGE

Возьмем конкретный индекс, например, SALES_CUST_BIX, и проверим   и количество его секций, и сколько места они занимают:

SQL> select partition_name, bytes
     2  from user_segments    
     3  where segment_name = 'SALES_CUST_BIX';

   PARTITION_NAME                  BYTES
   -----------------------         ----------
   SALES_1995                          65536
   SALES_1996                          65536
   SALES_H1_1997                       65536
   SALES_H2_1997                       65536
   SALES_Q1_1998                      327680
   SALES_Q1_2000                      327680
   SALES_Q1_2001                      327680
   SALES_Q1_2002                       65536
   SALES_Q1_2003                       65536
   SALES_Q2_1998                      262144
   SALES_Q2_1999                      327680
   SALES_Q2_2000                      327680
   SALES_Q2_2001                      327680
   SALES_Q2_2002                       65536
   SALES_Q2_2003                       65536
   SALES_Q3_1998                      327680
   SALES_Q3_1999                      327680
   SALES_Q3_2000                      327680
   SALES_Q3_2001                      327680
   SALES_Q3_2002                       65536
   SALES_Q3_2003                       65536
   SALES_Q4_1998                      327680
   SALES_Q4_1999                      327680
   SALES_Q4_2000                      327680
   SALES_Q4_2001                      327680
   SALES_Q4_2002                       65536
   SALES_Q4_2003                       65536

Этот индекс разделен на много секций, вплоть до 1995 года. В обычных приложениях обращение к очень старым данным, загруженным, например, в 1995 году, случается довольно редко. Следовательно, и индекс такой секции используется редко, если и вообще когда-либо используется. Несмотря на это, он занимает значительное место. Если такая секция каким-то образом была бы удалена,   то занимаемое её индексом пространство было бы освобождено. Однако отказаться от секции  нельзя, поскольку необходимо, чтобы её данные были в наличии.

В Oracle Database 11g Release 2 существует довольно простое решение: привести  индекс секции в неработоспособное (unusable) состояние, что заставит исчезнуть его сегмент, оставляя нетронутыми таблицы секций:

SQL> alter index SALES_CUST_BIX modify partition SALES_1995 unusable;
Index altered.
SQL> select partition_name, bytes
     2  from user_segments    
     3  where segment_name = 'SALES_CUST_BIX';
   
   PARTITION_NAME                   BYTES
   -----------------------          --------
   SALES_1996                          65536
   SALES_H1_1997                       65536
... выход усечен ... 

Примечание: более не существует сегмента SALES_1995. Сегмент был удален, поскольку индекс этой секции стал непригодным для использования. Если это сделать для многих старых разделов с несколькими   индексами, то можно освободить много пространства без потери данных - старых или новых.

Но что произойдет, если секция сделается раздел непригодной, а какой-то пользователь запросит из нее данные. Будет ли это ошибкой? Давайте посмотрим на примере.

Вот план оптимизации для запроса, который обращается к секции, индекс который доступен:

set autot on explain
   select count(1)
   from sales
   where cust_id = 611
   and time_id between to_date('01-jan-1999','dd-mon-yyyy') and to_date('01-mar-1999','dd-mon-yyyy')
Output:    Execution Plan
----------------------------------------------------------------------------------------------------------------
   | Id  | Operation                     | Name           | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
   ----------------------------------------------------------------------------------------------------------------
   |   0 | SELECT STATEMENT              |                |     1 |    13 |     5   (0)| 00:00:01 |       |       |
   |   1 |  SORT AGGREGATE               |                |     1 |    13 |            |          |       |       |
   |   2 |   PARTITION RANGE SINGLE      |                |    19 |   247 |     5   (0)| 00:00:01 |     9 |     9 |
   |   3 |    BITMAP CONVERSION COUNT    |                |    19 |   247 |     5   (0)| 00:00:01 |       |       |
   |   4 |     BITMAP AND                |                |       |       |            |          |       |       |
   |*  5 |      BITMAP INDEX SINGLE VALUE| SALES_CUST_BIX |       |       |            |          |     9 |     9 |
   |   6 |      BITMAP MERGE             |                |       |       |            |          |       |       |
   |*  7 |       BITMAP INDEX RANGE SCAN | SALES_TIME_BIX |       |       |            |          |     9 |     9 |
   ----------------------------------------------------------------------------------------------------------------
   Predicate Information (identified by operation id):
   ---------------------------------------------------
   5 - access("CUST_ID"=611)
      7 - filter("CUST_ID"=611 AND "TIME_ID"<=TO_DATE(' 1999-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND
                 "TIME_ID"<=TO_DATE(' 1999-03-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))

Теперь выполним тот же запрос для секции, [индекс] который был сброшен:

select count(1)
   from sales
   where cust_id = 611
   and time_id between to_date('01-jan-1995','dd-mon-yyyy') and to_date('01-mar-1995','dd-mon-yyyy')
Execution Plan
   ----------------------------------------------------------
   Plan hash value: 642363238
-------------------------------------------------------------------------------------------------
   | Id  | Operation               | Name  | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
   -------------------------------------------------------------------------------------------------
   |   0 | SELECT STATEMENT        |       |     1 |    13 |    36   (3)| 00:00:01 |       |       |
   |   1 |  SORT AGGREGATE         |       |     1 |    13 |            |          |       |       |
   |   2 |   PARTITION RANGE SINGLE|       |    19 |   247 |    36   (3)| 00:00:01 |     9 |     9 |
   |*  3 |    TABLE ACCESS FULL    | SALES |    19 |   247 |    36   (3)| 00:00:01 |     9 |     9 |
   -------------------------------------------------------------------------------------------------
   Predicate Information (identified by operation id):
   ---------------------------------------------------
   3 - filter("CUST_ID"=611 AND "TIME_ID"<=TO_DATE(' 1995-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND
                 "TIME_ID"<=TO_DATE(' 1995-03-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))

Запрос выполнился правильно; оптимизатор не возвратил ошибку. Так как индекс раздела был недоступен, просто было выполнено полное сканирование [секции] таблицы.

Это очень полезная возможность для тех баз данных, которые юридически обязаны держать записи в течение длительного периода времени. Это обеспечивает минимум требуемой памяти хранения, экономя пространство и деньги.

Заключение

Как можно видеть, стали радикально проще не только ранее трудоемкие команды, но в некоторых случаях открылись совершенно новые возможности для проводимых изо дня в день операций.

За время своей карьеры я видел в СУБД Oracle много функциональных изменений, которые являлись и достопримечательностями, и  поворотными пунктами в том, как делается бизнес. Описанные в этой статье возможности принадлежат к этой категории.