スキーマ管理共通の操作を高速かつ簡単にする新しい機能を使用して、データベース・オブジェクトをより効率的に管理します。
Oracle Database 11gには、仕事を簡単にする多くの機能だけでなく、時間のかかる共通の操作を仮想的な1行にまとめる機能も含まれています。 この記事では、このような機能について説明します。 DDLのWaitオプションAcme RetailersのDBAであるジルは、SALES表を変更してTAX_CODE列を追加しようとしています。 これは日常的な業務であり、次の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エラー・メッセージがすべてを語っています。 この表は現在、おそらくトランザクションによって使用されているので、排他的ロックを取得することはほぼ不可能です。 もちろん、表の行は永久にロックされているわけではありません。 セッションがコミットを実行すれば該当する行のロックは解除されますが、ロックの解除時間を獲得する前に、ほかのセッションが別の行を更新する可能性もあり、そうすると、排他的ロックを獲得できるタイムスライスは消滅します。 一般的なビジネス環境では、表を排他的にロックするためのウィンドウが定期的に開かれますが、そのときにDBAがalterコマンドを実行できるとは限りません。 ジルは排他的ロックを獲得するまで、何度も繰り返し同じコマンドを入力し続けることもできます。 しかし、Oracle Database 11g では、ジルにより適した選択肢を提供します。 DDLのWaitオプションです。 次を実行します。 SQL> alter session set ddl_lock_timeout = 10; Session altered.これで、このセッションのDDL文が排他的ロックを取得できなくても、エラーは発生しません。 その代わり10秒待機します。 その10秒間に、成功するかまたは期限が過ぎるか、どちらかに達するまで連続してDDL操作をリトライします。 次を実行すると、 SQL> alter table sales add (tax_code varchar2(10));文はハングし、エラーは発生しません。 ジルが排他的ロックを利用できるわずかな時間を繰り返し獲得しようとする代わりに、Oracle Database 11gに繰返しの試行を任せることができます。これは混んでいる番号に繰り返し電話をかけるプログラムのようなものです。 ジルはこの機能をとても気に入ったので、ほかの全員のDBAと共有しました。 システムが忙しい時間帯に表を変更するときには、誰もが同じ問題に直面するので、この新しい機能は非常に有用だということがわかります。 そこでジルが疑問に思ったのは、この動作をデフォルトにして、毎回ALTER SESSION文を実行しなくて済むようにできるのかということです。 これは可能です。 ALTER SYSTEM SET DDL_LOCK_TIMEOUT = 10を実行すれば、セッションはDDL操作中に指定した時間で自動的に待機します。 ほかの ALTER SYSTEM文のように、これは ALTER SESSION文によりオーバーライドできます。 デフォルト値で列を追加この機能だけでも満足しましたが、ジルは最初の問題に多少関係した、別の問題についても考えています。 列TAX_CODEを追加する必要があるのですが、それは非NULLでなければなりません。 NOT NULL列を空ではない表に追加する場合、デフォルト値である'XX'も指定する必要があります。 そこで、次のSQLを作成します。 alter table sales add tax_code varchar2(20) default 'XX' not null;しかし、ジルはここで手を止めます。 表SALESは巨大で、約4億行あります。 この文を実行すればOracleデータベースは問題なく行を追加するものの、制御が戻ってくるのは、すべての行を値'XX'に更新した後です。 4億行の更新には、非常に長い時間がかかるだけではなく、UNDOセグメントもいっぱいになり、大量のREDOが生成され、莫大なパフォーマンス・オーバーヘッドがかかります。 よって、この変更のために"休眠期間"、つまり機能停止を要求する必要があります。 しかし、Oracle Database 11gで、より適したアプローチはないのでしょうか。 あります。 Oracle Database 11gでは、上記の文によって表のすべてのレコードが更新されるわけではありません。それでは、 列の値が自動的に'XX'に設定される新規のレコードについては問題ありませんが、ユーザーが既存のレコードに対してこの列を選択するとNULLが返されるのでしょうか。 いいえ。そうではありません。ユーザーが既存のレコードに対してこの列を選択すると、Oracleデータベースはデータ・ディクショナリからデフォルト値に関する事実を取得し、それをユーザーに返します。 よって、新しい列をNULLではなくデフォルト値で定義できる上に、REDOおよびUNDOの生成の低下を招くこともないため、一石二鳥です。 仮想列Acmeのデータベースには、これまで見てきたように、SALES表が含まれます。 この表の構造は次のとおりです。
一部のユーザーには、売り上げの種類を識別するSALE_CATEGORYという列を追加する必要があります。 これはLOW、MEDIUM、HIGH、ULTRAのいずれかで、売上高と該当する顧客により決まります。 この列は適切なアクションが必要なレコードの識別や、関係する従業員への処理の転送に役立ちます。 次に、この列の値に対するロジックを示します。
この列は非常に重要なビジネス要件ですが、開発チームは必要なロジックを作成するためにコードを変更する必要はありません。 もちろん、sale_categoryという新しい列を表に追加し、前述のロジックを使用して列に移入するためのトリガーを作成することは可能です。 しかし、トリガー・コードとの間のコンテキスト・スイッチにより、パフォーマンスの問題が発生します。 Oracle Database 11g では、トリガーにたった1行のコードも作成する必要はありません。 その代わりに必要なのは、仮想列の追加だけです。 仮想列は列を追加する柔軟性を提供し、複雑さを増すことなく、そしてパフォーマンスに影響を与えることなく、ビジネス・センスを伝達します。 この表の作成方法は以下のとおりです。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.作成されるのはファンクション・ベースの索引です。 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 非表示の索引索引がユーザーの問合せに本当に利点があるのかどうか、疑問に思うことはないでしょうか。 1つの問合せには役立っても、ほかの10の問合せには害になっている場合があります。 索引は INSERT文に明らかに悪影響を及ぼし、場合によっては削除や更新も同様です。これは WHERE条件が索引の列を含むかどうかによって異なります。 関連する疑問として、索引が多少でも使用されているのかどうか、そして索引が削除されると問合せのパフォーマンスがどうなるのかというものがあります。 確かに、索引を削除して問合せに対する影響を確認することはできますが、これは容易なことではありません。 索引が実際に問合せに役立っていたとしたらどうしますか。 索引を復元しなければならず、そのためには再作成が必要です。 完全に再作成されるまで、誰も使用することはできません。 また、索引の再作成はコストのかかるプロセスでもあり、活用すべき多くのデータベース・リソースが必要です。 特定の問合せに対しては索引を使用できない状態にして、ほかの問合せには影響しないようにする何らかのオプションがあればどうでしょうか。 Oracle Database 11g より以前では、表のすべてのDMLが失敗してしまうため、 ALTER INDEX ... UNUSABLEを実行できませんでした。 しかし、今では非表示の索引を使用するオプションがあります。 簡単に言えば、索引をオプティマイザに対して"非表示"にし、問合せがその索引を使用しないようにできるのです。 その状態で、問合せが索引を使用する必要があるときは、ヒントとして明示的に指定する必要があります。 次に例を示します。 RES表があり、次に示すように、索引を作成したと仮定します。 SQL> create index in_res_guest on res (guest_id);この表と索引の分析後、次を実行すると、 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)次に索引を非表示にします。 SQL> alter index in_res_guest invisible; Index altered.今度は次のとおりに表示されます。 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操作中に代償を払いながら、同時に索引によるパフォーマンスの向上を得るということはありません。 読取り専用表Acmeのデータウェアハウス・システムの開発者であるロビンは、従来からある問題について考えています。 ETLプロセスの一部として、いくつかの表が異なる周期で更新されています。 更新されると、その表はビジネス・ルールに従いユーザーに公開されますが、ユーザーは変更すべきではないのです。 そして、ユーザーからこうした表へのDML権限を無効化する設定はありません。 ロビンが必要としているのは、スイッチのように動作して、表の更新を可能にしたり不可能にしたりすることです。 この操作の実装は、実際には非常に困難です。 ロビンにはどんな選択肢があるのでしょうか。 選択肢の1つは、 INSERT、 DELETE、および UPDATEの例外を発生させるトリガーを表に作成することです。 コンテキストの切替えを含むトリガーの実行は、パフォーマンスの面ではよくありません。 もう1つの選択肢は、"1=2"のような誤った文字列を常に返すVirtual Private Database(VPD)を作成することです。 表のVPDポリシーがこの機能を使用すると、FALSEを返してDMLは失敗します。 これはトリガーを使用する選択肢よりもパフォーマンスが高くなる可能性はありますが、"policy function returned error"のようなエラー・メッセージが表示されることが好ましくないのは明らかです。 しかし、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" エラー・メッセージは文字通りには操作を反映していませんが、目的に沿ったメッセージを伝えています。トリガーまたはVPDポリシーのオーバーヘッドもありません。 更新可能な表が欲しい場合、次のように読取り/書込み可能にする必要があります。 SQL> alter table trans read write; Table altered.これでDMLを実行しても問題ありません。 SQL> update trans set amt = 1 where trans_id = 1; 1 row updated.表が読取り専用モードのみである間、DMLは許可されません。すべてのDDL操作(索引の作成、パーティションの管理など)をおこなうことは可能です。 よって、この機能のとても有用な用途は表の管理となります。 表を読取り専用にして、必要なDDLを実行してから、再度読取り/書込み可能にすることができます。 表のステータスを確認するには、データ・ディクショナリ・ビューdba_tablesのread_only列を参照します。 SQL> select read_only from user_tables where table_name = 'TRANS'; REA --- NO ファイン・グレイン依存性追跡この機能は、例を用いると適切に説明できます。 次のように作成された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 VALIDVW_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 ------- VALIDvendor_name列を更新する新しいプロシージャを追加して、pkg_transパッケージを変更すると仮定します。 次に新しいパッケージの定義を示します。 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これにより高可用性の概念は大幅に高まります。 adjustファンクションはpkg_transパッケージの変更された部分を呼び出さないので、このファンクションを無効化する必要はなく、Oracle Database 11gでは無効化されません。 ただし、常にそうとは限りません。 前述の例で示したように、新しいサブ・コンポーネントがパッケージの最後に追加されるような方法でパッケージが変更された場合、依存する保存されたコードは無効化されません。 しかし、次のようにサブ・コンポーネントが最初に追加された場合、 create or replace package pkg_trans is procedure upd_vendor_name ... procedure upd_trans_amt ... end;Oracle Database 10gおよびそれ以前の場合と同様、依存するadjustファンクションは無効化されます。 これが発生するのは、新しいプロシージャが既存のプロシージャの前に挿入され、パッケージのスロット番号が変更されたため、無効化がトリガーされたからです。 プロシージャが既存のプロシージャの後に挿入されたときは、パッケージのスロット番号は変更されず、新しいスロット番号が追加されただけでした。 次に、依存性に関係する無効化を減少させるための共通のガイドラインを示します。
結論ご覧のとおり、これまでのように労力のかかるコマンドが非常に簡単になっただけではなく、場合によっては、毎日の業務を改善し得る新しい手法が実現しました。 これまで、Oracleデータベースの機能における多くの変更を見てきましたが、ビジネスの方法を再定義するような画期的なものがいくつかありました。 ここで説明したものは、そのようなカテゴリに入ります。 "Oracle Database 11g :DBAと開発者のための主要な機能"ホームページに戻るArup Nanda( arup@proligence.com)は、Oracleデータベーステクノロジのあらゆる分野で12年以上の経験を持つ優秀なOracle DBAであり、2003年の『Oracle Magazine』で"DBA of the Year"に選ばれました。 Arupは、オラクル関連のイベントの講演や雑誌の寄稿を頻繁におこなっている Oracle ACE Directorです。 また、『 RMAN Recipes for Oracle Database 11g: A Problem Solution Approach』を含む4冊の書籍を共同で執筆しています。 |