Database
技術記事
Ask Tom
March/April 2013 |
オラクルの技術者が、IDを引き継ぎ、外部表の制限をなくし、制約を復元します。
次のような要件がありますが、良い解決策が思い付きません。 USERAというユーザーがいて、このユーザーは表、データ、パッケージを所有しています。 また、別のUSERBというユーザーはそのUSERAのデータに対するSELECT権限を所有し(これは簡単)、独自のパッケージの作成権限もあります(これも簡単)。しかし、ここでの要件は、USERAにUSERBのパッケージの編集権限を付与することです。 ANY権限を付与しない限りこれは不可能ではないでしょうか。 単にUSERBのパスワードをUSERAに知らせるのが最善策ではないかと思いますが、 他にも何か解決策はありますか。
何をするにせよ、そのようにパスワードを知らせることはやめてください。 データベース内で誰が何をしたかが分からなくなるからです。 パスワードを知らせることは、トラブルの元になります。
AにBのIDを引き継がせること自体は可能です。ただし、監査を実施した際にAがBに成り代わって操作を実行したことを把握できるように、特定の期間に“AがBとして操作をした”という情報を管理する必要があります (さらに、AがBのIDを引き継ぐ権限に影響を及ぼさずに、Bが自分のパスワードを変更できるようにする必要もあります)。
この引き継ぎの方法を示すために、以下のように2つのアカウントを作成します。
SQL> create user a 2 identified by a; User created. SQL> grant 2 create session, 3 create procedure 4 to a; Grant succeeded. SQL> create user b 2 identified by b 3 default tablespace users 4 quota unlimited on users; User created. SQL> grant 2 create session, 3 create table, 4 create procedure 5 to b; Grant succeeded.
これでAとBという2人のユーザーが作成されました。この2人のユーザーは、それぞれ自分が必要とする権限のみを持っており、それ以外の権限はありません。 残りの作業は、“Bになる”権限をAに付与することです。以下のALTER USER文を使用して、この権限付与の操作を実行します。
SQL> alter user b 2 grant connect through a; User altered.
これで、Aは“Bになる”こと、およびBのコードを編集することが許可されました。 これを示すために、以下で、Bとしてプロシージャを作成し、その後Aにそのプロシージャの編集を許可します。
SQL> connect b/b Connected. SQL> create 2 procedure p 3 as 4 begin 5 null; 6 end; 7 / Procedure created.
次に、Aとしてログインし、Aのまま、BのIDを引き継ぎます。
SQL> connect a/a Connected. SQL> show user USER is "A" SQL> connect a[b]/a Connected. SQL> show user USER is "B"
ここでは、AがSQL*PlusでAのユーザー名とパスワードを使用して接続し、ユーザーBになることができた点に注意してください。 今、AはBとしてログインしているので、Bの権限を使用して以下の操作を実行できます。
SQL> create or replace
2 procedure p
3 as
4 begin
5 dbms_output.put_line
( 'new stuff' );
6 end;
7 /
Procedure created.
SQL> create table t2 ( x int );
Table created.
SQL> grant execute on p to a;
Grant succeeded.
ここでは、Aがストアド・プロシージャPを編集して置き換えただけではなく、BのCREATE TABLE権限も使用しています。 これは意図した操作ではないでしょう。そのため、この操作を防止する方法を後で紹介します。 しかしその前に、変更されたコードが期待どおりに動作することを確認しましょう。
SQL> connect a/a Connected. SQL> exec b.p new stuff PL/SQL procedure successfully completed.
確かに、ユーザーAはBが所有するプロシージャを更新できました。
では次に、CREATE TABLEの問題に対処します。 常に“最小限の権限を付与する”という考え方に従うべきであるため、AはBのCREATE PROCEDURE権限とCREATE SESSION権限のみを使用でき、CREATE TABLE権限は使用できないように設定します。 これは、ロールを使用することで設定できます。
ユーザーBを以下のように再作成します。
SQL> create role b_role1; Role created. SQL> create role b_role2; Role created. SQL> grant 2 create procedure, 3 create session 4 to b_role1; Grant succeeded. SQL> grant 2 create table 3 to b_role2; Grant succeeded. SQL> create user b 2 identified by b 3 default tablespace users 4 quota unlimited on users; User created. SQL> grant b_role1 to b; Grant succeeded. SQL> grant b_role2 to b; Grant succeeded.
この手順では、ロールを使用してBにプロシージャと表の作成権限を付与している点と、プロシージャの作成権限と表の作成権限を2つのロールに分けている点に注意してください。 これで、特定のロールのみを有効にして、ユーザーAに対してB経由での接続権限を付与できます。
SQL> alter user b 2 grant connect through a 3 with role b_role1; User altered.
この結果、ユーザーAが“Bになった”場合に、B_ROLE1のみが有効になります。ユーザーAにはCREATE SESSION権限とCREATE PROCEDURE権限のみが付与され、CREATE TABLE権限は付与されません。
SQL> connect a[b]/a
Connected.
SQL> show user
USER is "B"
SQL> create or replace
2 procedure p
3 as
4 begin
5 dbms_output.put_line
( 'new stuff' );
6 end;
7 /
Procedure created.
SQL> create table t2 ( x int );
create table t2 ( x int )
*
ERROR at line 1:
ORA-01031: insufficient privileges
SQL> grant execute on p to a;
Grant succeeded.
ご覧のとおり、ユーザーAはユーザーBとして接続してプロシージャを作成できますが、表を作成することはできません。一方、ユーザーBは以下のように表を作成できます。
SQL> connect b/b Connected. SQL> create table t ( x int ); Table created.
このように別のユーザー経由で接続する機能はプロキシ認証と呼ばれており、3層の環境におけるミドル層の認証など、他の環境で使用する場合に非常に便利です。 詳細については、を参照してください。
外部表の定義内でSQLLDR BOUNDFILLERキーワードを使用できますか。 以下のようなデータがあります。
"Jane Doe",2008,03,18,23,59,4 "Tom Thomas",2011,10,31,18,00,59 "Bill Williams",2012,02,31,01,10,5
フィールドは名前と日時を表しています。 ここで、以下のようなフィールド・リストを使用して外部表の定義を作成しようとしました。
( patient_name ,v_yyyy boundfiller char ,v_mm boundfiller char ,v_dd boundfiller char ,v_hh boundfiller char ,v_mi boundfiller char ,v_ss boundfiller char ,service_date "to_date(:v_yyyy || :v_mm || :v_dd || :v_hh || :v_mi || :b_ss , 'YYYYMMDDHH24MISS')" )
しかし、以下のエラー・メッセージが発生しました。このメッセージは、BOUNDFILLERキーワードが認識されないことを示しています。
ORA-29913: error in executing ODCIEXTTABLEOPEN callout ORA-29400: data cartridge error KUP-00554: error encountered while parsing access parameters KUP-01005: syntax error: found "identifier": expecting one of: "binary_double, binary_float, comma, char, date, defaultif, decimal, double, float, integer, (, nullif, oracle_date, oracle_number, position, raw, recnum, ), unsigned, varrawc, varchar, varraw, varcharc, zoned" KUP-01008: the bad identifier was: boundfiller KUP-01007: at line 13 column 21
BOUNDFILLERを使用できない場合、外部表の定義内で複数のフィールドを連結する別の方法について教えてください。
これは(ある意味)ひっかけ問題です。 外部表を使用する場合は、20世紀のレガシーなデータ・ロード・ツールであるSQL Loader(SQLLDR)が持つ制約は存在しないということを忘れてしまっています。 外部表を使用すれば、SELECT文の強力な機能を駆使して、何でも実行できます。
このことを示すために、SQLLDR自体を使用して、SQLLDRから外部表にアップグレードするためのもっとも簡単な方法について説明します。 質問内の制御ファイルを以下のように使用します。
LOAD DATA INFILE '/home/tkyte/t.dat' INTO TABLE t REPLACE FIELDS TERMINATED BY ',' trailing nullcols ( patient_name ,v_yyyy boundfiller char ,v_mm boundfiller char ,v_dd boundfiller char ,v_hh boundfiller char ,v_mi boundfiller char ,v_ss boundfiller char ,service_date "to_date(:v_yyyy || :v_mm || :v_dd || :v_hh || :v_mi || :v_ss ,'YYYYMMDDHH24MISS')" )
次に、これをSQLLDRで実行します。この際、この内容を外部表に変換するようにSQLLDRに指示します。
$ sqlldr / t.ctl external_table=generate_only
この後、この処理によって生成されたログ・ファイルを調査すれば、CREATE TABLE文に加えて、ジョブの最後でINSERT AS SELECT文も実行されていることが分かります。リスト1に、このログ情報を示します。
コード・リスト1:外部表作成時のログ情報
CREATE TABLE statement
CREATE TABLE "SYS_SQLLDR_X_EXT_T"
(
"PATIENT_NAME" VARCHAR2(30),
"V_YYYY" VARCHAR2(255), "V_MM" VARCHAR2(255),
"V_DD" VARCHAR2(255), "V_HH" VARCHAR2(255),
"V_MI" VARCHAR2(255), "V_SS" VARCHAR2(255),
"SERVICE_DATE" VARCHAR(4000)
)
ORGANIZATION external
(
TYPE oracle_loader
DEFAULT DIRECTORY MY_DIR
ACCESS PARAMETERS
( RECORDS DELIMITED BY NEWLINE CHARACTERSET US7ASCII
BADFILE 'MY_DIR':'t.bad'
LOGFILE 't.log_xt' READSIZE 1048576
FIELDS TERMINATED BY "," LDRTRIM
MISSING FIELD VALUES ARE NULL
REJECT ROWS WITH ALL NULL FIELDS
( "PATIENT_NAME" CHAR(255) TERMINATED BY ",",
"V_YYYY" CHAR(255) TERMINATED BY ",",
"V_MM" CHAR(255) TERMINATED BY ",",
"V_DD" CHAR(255) TERMINATED BY ",",
"V_HH" CHAR(255) TERMINATED BY ",",
"V_MI" CHAR(255) TERMINATED BY ",",
"V_SS" CHAR(255) TERMINATED BY ",",
"SERVICE_DATE" CHAR(255) TERMINATED BY ","
)
) location('t.dat')
)REJECT LIMIT UNLIMITED
INSERT AS SELECT statement
INSERT /*+ append */ INTO T
(
PATIENT_NAME,
SERVICE_DATE
)
SELECT
"PATIENT_NAME",
to_date("V_YYYY" || "V_MM" || "V_DD" || "V_HH" || "V_MI" || "V_SS" ,
'YYYYMMDDHH24MISS')
FROM "SYS_SQLLDR_X_EXT_T"
外部表からデータを読み取る際には任意のSQLを使用できるため、外部表の定義内ではなく、データを読み取る段階で任意のロジックを適用できます。
主キー制約と関連する索引が設定された表があります。 フラッシュバックを使用して表をリストアできますが、表に関連付けられた制約や索引はどうすればリストアできるでしょうか。 索引や制約の再作成は可能なのですが、Oracle Flashbackテクノロジーを使用して索引や制約はリストアできるのでしょうか。
索引と制約は実際にフラッシュバックによってリストアされますが、ユーザーはそのことを認識していない可能性があります。 索引と制約ではリサイクル・ビン名が維持されます。それらの名前はBIN$から始まり、その後ランダムに見える文字列が続きます。 索引と制約は表と共にリストアされますが、それらの名前は消失しています。 そのため、名前を変更する作業が必要になります。 以下に例を挙げます。
alter index "BIN$/yO2LoFDTmObx1GIQtwxOA==$0" rename to PK_SR_TEST1;
しかし、索引名や制約名が分からない場合や、名前を変更する対象が多数ある場合に問題となります。 多くの場合、データ辞書に対してフラッシュバック問合せを使用することで、この問題を解決できます。 これを示すために、以下のように1つの表を作成します。この表を、誤ってすぐに削除します(その後、この表の“削除を取り消します”)。
SQL> create table t 2 ( x int, 3 constraint t_pk primary key(x), 4 constraint check_x check(x>0) 5 ); Table created.
次に、制約や索引が存在した期間のうちの特定の時間を知る必要があります。 表を削除した正確な時間を把握する必要はなく、索引と制約を含む状態で表が存在したいずれかの時間が分かれば良いです。 この例ではシステム変更番号(SCN)を時間として使用しますが、通常はタイムスタンプ(日付と時刻)を使用することになるでしょう。 このデモの後半で使用するために、現在のSCNを変数に保存しておきます。
SQL> column SCN new_val S
SQL> select dbms_flashback.get_system
_change_number SCN
2 from dual;
SCN
——————————————
106788340
では次に、表を誤って(ここでは故意に)削除します。
SQL> drop table t; Table dropped.
この表のリストアはFLASHBACK TABLEを使用すれば簡単です。
SQL> flashback table t 2 to before drop; Flashback complete.
しかし、以下のように、索引はBIN$xxxxxという名前になっています。
SQL> column index_name new_val I SQL> select index_name 2 from user_indexes 3 where table_name = 'T'; INDEX_NAME ————————————————————————————————————————— BIN$zwMim2IndhbgQwEAAH/duw==$0
ここで必要となる作業は、そのビューに対してフラッシュバック問合せを実行することですが、その前にまずは権限付与を2回実行する必要があります。
SQL> connect / as sysdba Connected. SQL> grant flashback on user_indexes to ops$tkyte; Grant succeeded. SQL> grant flashback on user_constraints to ops$tkyte; Grant succeeded.
権限付与が完了したら、USER_INDEXESビューに対してフラッシュバック問合せを実行できます。
SQL> connect / Connected. SQL> column index_name new_val OI SQL> select index_name 2 from user_indexes as of scn &S 3 where table_name = 'T'; old 2: from user_indexes as of scn &S new 2: from user_indexes as of scn 106789036 INDEX_NAME ————————————————————————————————————————— T_PK
これで、現在の索引名と以前の索引名が分かりましたので、以前の索引名を以下のように変更できます。
SQL> alter index "&I" rename to "&OI"; old 1: alter index "&I" rename to "&OI" new 1: alter index "BIN$zwNBF4ITdtLgQwEAAH/Atw==$0" rename to "T_PK" Index altered.
同じロジックをUSER_CONSTRAINTSビューに対しても使用することで、以前の制約名と現在の制約名を同様に取得して、以前の制約名を変更できます。
あるOracle Database 10gインスタンスを受け継いだのですが、現在、データベースの実行が遅いという報告を多く受けています。 このデータベースはWebベースのアプリケーションをサポートしており、アプリケーション・サーバーの接続プールに問題があることは分かっています。しかし、データベースを調査すると、非アクティブ・セッション数が約600に上り、アクティブ・セッションは90しかなく、そのうち約45のセッションがブロックされています。
ユーザーは600人もいないため、多くの非アクティブ・セッションが孤立しているのではないかと見ています。 対策として、少し時間が経った後に非アクティブ・セッションを有効期限切れにするようにプロファイルを設定する方法を検討しています。 ブロックされたセッションの詳細な診断方法について、何か提案はありますか。
非アクティブ・セッションを終了するようなプロファイルの設定は絶対にしないでください。 接続プールは常にデータベース内にセッションを保有するため、セッションの多くが、非アクティブ期間中は、長期間アイドル状態となっている可能性があります。 これらのセッションを終了すると、接続プール自体を終了してしまうことになります。そうなれば、アプリケーションが接続できなくなったことに関するエラー・メッセージが出力され始めます(問題が悪化します)。
では、目下の問題について考えましょう。 問題は、開発者のコードに接続(おそらくは接続とカーソル)のリークがあるということです。 その場合、コードに以下のような記述があります。
try
{
grab a connection
prepare statement
execute statement
<<<==== this locks some resource
close statement
prepare statement2
execute statement2
<<<==== this fails
close statement2
commit
release connection back to pool
}
catch (e exception)
{
print out a silly message somewhere
}
開発者のコードにこのような記述があるという確信があります。私はこれまで何度もこのようなエラー・パターンを見てきました。 これは時折発生するエラーであり(1回でもうんざりするものですが)、例外処理に不備があるために、接続を解放してプールに戻す処理が飛ばされています。 未処理のトランザクションにロック済みのリソースがあり、そのリソースが接続ハンドルに関連付けられているため、その接続ハンドルを再度利用できなくなります。
この状況を簡単に証明するために、テスト/QA環境内で接続プールの最小サイズを1、最大サイズを1に設定して、リグレッション・テストをひととおり実行するように開発者に伝えてください。 そのテストが永続的に実行できず、途中で停止する(接続を使い果たす)ようであれば、このバグは開発者のコード内に存在しています。 私には、そのコードがすぐに停止するという確信があります。
提案としては、接続プール設定上でハンドルを取得してください。 常にMINとMAXを同じ値に設定し、かつ適度な数値に設定します(接続プール数が動的に増加しないようにします)。 ナレーション付きのビデオ(bit.ly/11YQuhB)で非常に説得力のある証拠を示しています。このビデオを見て、最小値と最大値を同一にするという点が非常に重要であることを理解してください(これは、システムの安定性を確保するために実施できる最重要事項です)。
開発/QA環境で、接続プールのサイズを1個の接続として設定した上で、上記の接続テストを実行し、永続的に実行されるようになるまでこのテストを繰り返します。
接続プール内の接続数を制御できるようになるまでは、Oracle Enterprise Managerを使用してブロック/ブロッカ・グラフを確認した上で、他のセッションをブロックしており、かつ一定の期間アイドル状態にあるセッションを終了します。 この解決策はお粗末ではあるのですが、開発者がこの重大なバグを修正するまでは、“最先端の”解決策です。
ただし、アイドル状態のセッションを単純に終了することは避けてください。 このセッションに関連付けられている接続プール内の接続が、しばらくの間使用されていないだけの可能性もあります。 Oracle DatabaseのResource Manager機能を保護メカニズムとしてかならず使用して、システム内で同時にアクティブになるセッション数を制限してください。 600個の接続がすべてアクティブになった場合(またはアクティブになろうとした場合)、600ものCPUコアは存在しないと見て間違いないため、マシンが崩壊します。 同時に実行するセッション数を適度な数値に制限しましょう。この根拠については、前述のビデオを参照してください。 おそらく、ある特定の時点で同時にアクティブにできるセッション数は、10*cpu_count程度でしょう(セッションの実行内容によっては、10*cpu_countよりも少ない場合もあります)。
次のステップ
その他の記事、書籍
詳細情報 |