v$session, v$active_session_history の blocking_session 列で待機をブロックしているセッションが分かるが、階層問合せによって待機をツリー状に表示して辿ることで最終的にブロックしているセッションを特定する。
v$session, v$active_session_history の blocking_session 列でそのセッションをブロックしているセッションの SID が分かる。 (例えばそのセッションが要求しているロックを保持しているセッションの SID)
階層問合せ(もしくは 再帰クエリ)によって、親子関係があるような階層構造、ツリー構造を持つデータの問合せができる。
Oracle Database では独自の CONNECT BY
句を使った問合せを行う。
(11gR2 からは業界標準 SQL99 の再帰 with
句での問合せも可能)
例えば EMP 表には各従業員の EMPNO 列と、その上位マネージャの EMPNO である MGR 列があるが、以下の階層問合せでマネージャと部下の親子関係を表示できる。 (以下は Wikipedia 再帰クエリ から抜粋)
1 2 3 4 |
|
LEVEL EMPLOYEE EMPNO MGR
------ --------------- ------ ------
1 KING 7839
2 JONES 7566 7839
3 SCOTT 7788 7566
4 ADAMS 7876 7788
3 FORD 7902 7566
4 SMITH 7369 7902
2 BLAKE 7698 7839
3 ALLEN 7499 7698
3 WARD 7521 7698
3 MARTIN 7654 7698
3 TURNER 7844 7698
3 JAMES 7900 7698
2 CLARK 7782 7839
3 MILLER 7934 7782
実際に複数セッションで待機状態を作って試す。
セッション1:
UPDATE emp SET sal = sal WHERE empno = 7369;
セッション2:
UPDATE emp SET sal = sal WHERE empno = 7499;
UPDATE emp SET sal = sal WHERE empno = 7369;
(セッション1を待機)セッション3:
UPDATE emp SET sal = sal WHERE empno = 7521;
UPDATE emp SET sal = sal WHERE empno = 7499;
(セッション2を待機)セッション4:
UPDATE emp SET sal = sal WHERE empno = 7566;
UPDATE emp SET sal = sal WHERE empno = 7369;
(セッション1を待機)セッション5:
UPDATE emp SET sal = sal WHERE empno = 7521;
(セッション3を待機)セッション6:
UPDATE emp SET sal = sal WHERE empno = 7499;
(セッション2を待機)セッション7:
UPDATE emp SET sal = sal WHERE empno = 7566;
(セッション4を待機)階層問合せを行い、待機状態を確認する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
SQL> SELECT
LEVEL,
LPAD(' ', 2 * (LEVEL - 1)) || sid hierarchy,
SYS_CONNECT_BY_PATH(sid, '<-') path,
sid,
blocking_session,
event
FROM v$session
START WITH blocking_session IS NULL
CONNECT BY PRIOR sid = blocking_session
ORDER SIBLINGS BY sid
/
LEVEL HIERARCHY PATH SID BLOCKING_SESSION EVENT
----- -------------------- -------------------- ----- ---------------- --------------------------------
1 1 <-1 1 pmon timer
1 2 <-2 2 VKTM Logical Idle Wait
1 3 <-3 3 DIAG idle wait
...(*snip*)
1 141 <-141 141 SQL*Net message from client
2 10 <-141<-10 10 141 enq: TX - row lock contention
3 20 <-141<-10<-20 20 10 enq: TX - row lock contention
3 136 <-141<-10<-136 136 10 enq: TX - row lock contention
4 143 <-141<-10<-136<-143 143 136 enq: TX - row lock contention
2 19 <-141<-19 19 141 enq: TX - row lock contention
3 142 <-141<-19<-142 142 19 enq: TX - row lock contention
つまり、セッション1(SID=141)が最終的にブロックしている起源(ルート)であることがわかる。
なお、LEVEL
疑似列により階層のレベルが分かる。また、SYS_CONNECT_BY_PATH
により階層のパスを表すことができる。
v$active_session_history では、アイドル以外の待機状態もしくは CPU 使用中のセッションしか現れない。 上述の例のように最終的に待機させていたセッション(セッション1:SID=141)がアイドル状態だと、ルートのセッションが現れないので単純には階層表示できない。 従って、blocking_session がすべて現れるように工夫する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
|
SQL> SELECT MAX(sample_id) FROM v$active_session_history;
MAX(SAMPLE_ID)
--------------
244679
SQL> define sample_id = &sample_id
Enter value for sample_id: 244679
SQL> WITH x AS
(
SELECT session_id, blocking_session, session_state, event
FROM v$active_session_history
WHERE sample_id = &sample_id
UNION ALL
SELECT blocking_session, null, null, 'IDLE?'
FROM v$active_session_history
WHERE sample_id = &sample_id
AND blocking_session NOT IN (SELECT session_id
FROM v$active_session_history
WHERE sample_id = &sample_id)
GROUP BY blocking_session
)
SELECT
LEVEL,
LPAD(' ', 2 * (LEVEL - 1)) || session_id hierarchy,
SYS_CONNECT_BY_PATH(session_id, '<-') path,
session_id,
blocking_session,
session_state,
event
FROM x
START WITH blocking_session IS NULL
CONNECT BY PRIOR session_id = blocking_session
ORDER SIBLINGS BY session_id
/
LEVEL HIERARCHY PATH SESSION_ID BLOCKING_SESSION SESSION_STATE EVENT
----- -------------------- -------------------- ---------- ---------------- ------------- --------------------------------
1 141 <-141 141 IDLE?
2 10 <-141<-10 10 141 WAITING enq: TX - row lock contention
3 20 <-141<-10<-20 20 10 WAITING enq: TX - row lock contention
3 136 <-141<-10<-136 136 10 WAITING enq: TX - row lock contention
4 143 <-141<-10<-136<-143 143 136 WAITING enq: TX - row lock contention
2 19 <-141<-19 19 141 WAITING enq: TX - row lock contention
3 142 <-141<-19<-142 142 19 WAITING enq: TX - row lock contention
アイドル状態のために現れないセッションを UNION ALL
で仮想的に v$active_session_history に追加したインラインビュー x を使用した。
(もっといいやり方があるかもしれない。)
システムコール ptrace(2) を使用してデバッグする。 ptrace を使用して簡易的なデバッガを作成することで、GDB などの汎用デバッガより細かい制御を行うことも可能になる。
別のプロセスにアタッチしたり、別のプロセスのメモリやレジスタを参照、書込み等を行うことができるシステムコール。 デバッガを実装するために使用される。
1 2 3 4 |
|
request
にアタッチしたプロセスに何を行うかを指定する。
例えば、以下が指定できる。(参考: man ptrace
)
PTRACE_ATTACH
pid
で指定したプロセスにアタッチする。アタッチしたプロセスは子プロセスとしてトレースできるようにする。(引数 addr
と data
は無視される。)
PTRACE_PEEKTEXT, PTRACE_PEEKDATA
メモリの addr
の位置を参照する。(引数 data
は無視される。)
PTRACE_PEEKUSR
USER 領域のオフセット addr
の位置を参照する。(引数 data
は無視される。)
PTRACE_POKETEXT, PTRACE_POKEDATA
data
をメモリの addr
の位置に書込む。
PTRACE_POKEUSR
data
を USER 領域のオフセット addr
の位置に書き込む。
PTRACE_GETREGS
レジスタの値を data
にコピーする。data
は user_regs_struct 構造体(<linux/user.h>)。(引数 addr
は無視される。)
PTRACE_SETREGS
data
をレジスタにコピーする。data
は user_regs_struct 構造体(<linux/user.h>)。(引数 addr
は無視される。)
INT X
(X は 0~255 の数値) は x86, x86-64 プロセッサのインストラクションで、ソフトウェア割り込みを発生させる。
特に INT 3
(オペコード: 0xCC
) により SIGTRAP シグナルが発生し、処理が中断される。これは、デバッガがブレイクポイントを設定するために使用される。
x86-64 プロセッサのレジスタ RIP (インストラクションポインタ)には次に実行される命令のアドレスが格納されている。(プログラムカウンタとも呼ばれる。)
ptrace では、アタッチしたプロセスのメモリやレジスタを参照したり、書込むといった原始的なことしかできない。
したがって、ptrace によってブレイクポイントを設定してデバッグするには、以下のような手順が必要。
PTRACE_ATTACH でターゲットのプロセスにアタッチ。
PTRACE_PEEKTEXT でブレイクポイントを設定したいアドレスの命令を一時的に保存しておく。
PTRACE_POKETEXT で 2. のアドレスの命令を INT 3 (0xCC)に書換える。
PTRACE_CONT でプロセスを再開させる。
waitpid(2) でブレイクするのを待つ。
プロセスが INT 3 で書換えたアドレスでブレイクする。
次に実行する命令のアドレスを示すレジスタ IP は INT 3 で書換えたアドレス(ブレイクポイントを設定したアドレス)の次の位置(+1バイトの位置)を指しているので、PTRACE_GETREGS/PTRACE_SETREGS で IP をブレイクポイントを設定したアドレスに書換えて戻す。
PTRACE_POKETEXT で 3. で書換えた命令を 2. で保存していたオリジナルに戻す。(ブレイクポイントを設定したアドレスのメモリを元の命令に戻す。)
これでブレイクしたい位置で中断されているので、レジスタを見たり、メモリを参照したりして任意のデバッグを行う。
再度ブレイクさせたかったら 2. に戻る。 ただし、同じ命令のアドレスでブレイクさせたかったら、PTRACE_SINGLESTEP で 1 命令だけ進めてから、2. に戻る。(そうでないと現在の IP 位置を INT 3 で書換えてしまい、すぐにブレイクして処理が進まなくなってしまう。)
デバッグが終わったら、PTRACE_DETACH でプロセスからデタッチする。
上述のような方法でブレイクポイントを設定してデバッグする簡易的なデバッガを作成する。
引数としてアタッチしたいプロセスの PID と、ブレイクするアドレスを指定して、ブレイクポイントでレジスタ RDI の値を表示する簡易的なデバッガ。
ブレイクするアドレスとして関数の先頭アドレスを指定すれば、レジスタ RDI には第 1 引数が入っているはずなので、このプログラムを使って対象の関数が実行される度に第 1 引数を表示することができる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
|
なお、レジスタの書換えは以下のように PTRACE_GETREGS/PTRACE_SETREGS でなくて、PTRACE_POKEUSER でも行けた。(43行目)
1
|
|
もちろん、PTRACE_GETREGS/PTRACE_SETREGS を使って以下のようにしてもよい。
1 2 3 4 5 |
|
あと、デバッグを止めるときはカレントディレクトリに quit
というファイルを置くことにした。($ touch ./quit
とかで作ればよい。)
コンパイルしておく。
$ gcc -o ptrace_demo ptrace_demo.c
デバッグ対象として以下の簡単なプログラムを用意。1秒毎に func 関数が呼ばれている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
コンパイルしておく
$ gcc -o sample sample.c
上記の func 関数が呼ばれる度に第 1 引数(レジスタ RDI) の値を出力するデバッグを行う。
まず、func のアドレスを確認。
$ nm sample | grep func
00000000004004d8 T func
func のアドレスは 0x00000000004004d8 。
プログラムを実行。
$ ./sample
hello, world: 1
hello, world: 2
hello, world: 3
hello, world: 4
hello, world: 5
hello, world: 6
...<略>
PID, アドレスを指定してこの簡易デバッガを実行してみる。
$ ps -ef | grep sample
hashi 21250 32254 0 17:33 pts/2 00:00:00 ./sample
$ ./ptrace_demo 21250 0x00000000004004d8
Breakpoint at 0x4004d8.
Continuing.
Breakpoint.
arg 1: 27
Breakpoint at 0x4004d8.
Continuing.
Breakpoint.
arg 1: 28
Breakpoint at 0x4004d8.
Continuing.
Breakpoint.
arg 1: 29
Breakpoint at 0x4004d8.
Continuing.
Breakpoint.
...<略>
func でブレイクして、第 1 引数(レジスタ RDI) の値が出力できている。
止めるときは同じディレクトリに quit という名前のファイルを作れば、デタッチしてデバッグ終了する。
$ touch quit
上述の方法で GDB で以下のようにしたときと同じようにデバッグすることができた。
$ gdb ./sample 21250
(gdb) b func
Breakpoint 1 at 0x4004dc
(gdb) command
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just "end".
>p $rdi
>c
>end
(gdb) c
Continuing.
Breakpoint 1, 0x00000000004004dc in func ()
$1 = 27
Breakpoint 1, 0x00000000004004dc in func ()
$2 = 28
Breakpoint 1, 0x00000000004004dc in func ()
$3 = 29
...<略>
ptrace を使用したデバッガを作るとで GDB より柔軟に情報採取をすることも可能になる。
LD_PRELOAD, dlsym, GCC拡張機能によって共有ライブラリの関数の呼出し前後で任意の処理を実行する。
環境変数 LD_PRELOAD に共有ライブラリを指定すると、そのライブラリがすべてのライブラリに先立ってロードされる。
これを利用して通常ロードしている共有ライブラリ内の関数を置き換えることができる。(参考: man ld.so
)
dlsym(3) は、シンボル名の文字列を引数に取り、そのシンボルのアドレスを返す。
これを利用して、関数のアドレスを得ることができる。(参考: man dlsym
)
__attribute__((constructor))
, __attribute__((deconstructor))
GCC 拡張で __attribute__
キーワードと共に関数の属性(attribute)を指定することができる。(参考: info gcc
–> “C Extensions” –> “Function Attributes”)
constructor 属性が指定された関数は、main() 関数が呼ばれる前に実行される。 deconstructor 属性が指定された関数は、main() 関数が完了するか exit() が呼ばれた後で実行される。
実際に LD_PRELOAD, dlsym, GCC拡張機能により共有ライブラリの関数の呼出し前後で任意の処理を実行してみる。
今回使用するのは共有ライブラリを使用する以下のプログラム。
1
|
|
1 2 3 4 5 6 7 8 9 10 11 12 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
コンパイル方法と実行結果は以下。main 関数から共有ライブラリ foo.so 内の関数 foo() を呼んでいる。
$ gcc -g -Wall -fPIC -shared -o libfoo.so foo.c
$ gcc -g -Wall -L. -lfoo main.c
$ ./a.out
[main] hello
[foo ] hello
[foo ] request: ten plus twenty
[foo ] bye
[main] return from foo: 30
[main] bye
上記の共有ライブラリ foo.so 内の関数 foo() の前後で処理をするために以下の bar.c から共有ライブラリ bar.so を作成する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
|
以下、コードの解説。
1 2 |
|
GCC 拡張機能により、関数 init() に constructor 属性を設定し、main() 関数が呼ばれる前に実行されるようにしている。 また、関数 fini() に destructor 属性を設定し、プログラム終了直前に実行されるようにしている。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
main() 関数が呼ばれる前に実行される init() 関数内で、オリジナルの foo() 関数(のアドレス)をグローバル変数 original_foo に格納している。dlsym() の引数に RTLD_NEXT を指定することで現在のライブラリ(この例では bar.so)以降で最初に関数が現れるところを探す。この機能により別の共有ライブラリ(この例では foo.so)の関数へのラッパーを提供することができる。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
オリジナルの foo() のラッパー関数として同一関数名を定義し、先ほど格納したオリジナルの関数を呼んでいる。その前後で任意の処理を実行している。(この例では引数や返値を出力している)
なお、この例のようにオリジナルの関数の引数の数は合わせたが、引数や返値の型が一致していなくても long
や void *
などオリジナルの型が入るような型であれば暗黙的に型変換してうまく動いてくれるようだ。(クローズドソースの共有ライブラリとかオリジナルの関数の引数の型が分からないようなケースでも推測してある程度合わせればOKということ。)
コンパイルは以下のように実施して libbar.so を作成する。RTLD_NEXT
マクロを使用するため、-D_GNU_SOURCE
を加えていることと、dlsym() を使用するために -ldl
を加えていることに注意。
$ gcc -g -Wall -D_GNU_SOURCE -fPIC -shared -o libbar.so bar.c -ldl
あとは、作成した libbar.so を LD_PRELOAD で指定してロードされるようにすればよい。 元の実行ファイルや共有ライブラリは再コンパイル、リリンクすることなしに foo() 関数を置き換えて、前後に処理を実行できている。 これを利用すれば、既存の共有ライブラリの関数の引数を出力するなど、デバックが容易にできる。
$ env LD_PRELOAD=./libbar.so ./a.out
[bar ] init
[bar ] orignal_foo: 0x2b5fdd50d55c
[main] hello
[bar ] before foo arg1: 10, arg2: 20, arg3: 0x400785("ten plus twenty")
[foo ] hello
[foo ] request: ten plus twenty
[foo ] bye
[bar ] after foo ret: 30
[main] return from foo: 30
[main] bye
[bar ] fini
LD_PRELOAD しなかったときの出力と比較して、foo() 関数の前後で引数情報などが出力できている。 また、main 関数前後でも処理が実行されていることが分かる。
Linux x86-64 の呼出規約(calling convention)を gdb で確認する。
プログラムで関数を呼び出す際に、レジスタやスタックを使いどのように引数を渡すか、戻り値をどのように受け取るかは呼出規約(calling convention)で決められている。
Linux x86-64 では、以下のような呼出規約になっている。 (Wikepedia x86 calling conventions, 呼出規約 から抜粋)
The calling convention of the System V AMD64 ABI is followed on Solaris, GNU/Linux, FreeBSD, and other non-Microsoft operating systems. The first six integer or pointer arguments are passed in registers RDI, RSI, RDX, RCX, R8, and R9, while XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6 and XMM7 are used for floating point arguments. For system calls, R10 is used instead of RCX. As in the Microsoft x64 calling convention, additional arguments are passed on the stack and the return value is stored in RAX.
より詳細には System V Application Binary Interface AMD64 Architecture Processor Supplement を参照。
上記の呼出規約を実際のプログラムで確認してみる。
使用するサンプルプログラムは以下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
実行結果は以下。
$ gcc -g -o sample1 sample1.c
$ ./sample1
sum: 495
gdb から、関数 func から関数 sum を呼び出している個所を確認する。
disas/m
というふうに修飾子 /m を指定するとソースと共に表示されて分かりやすくなる。
(ちなみに objdump -d -S ./sample1
としてもソースと関数の逆アセンブル結果が出力される)
$ gdb ./sample1
(gdb) set height 0
(gdb) disas/m func
Dump of assembler code for function func:
18 {
0x00000000004004da <func+0>: push %rbp
0x00000000004004db <func+1>: mov %rsp,%rbp
0x00000000004004de <func+4>: sub $0x30,%rsp
19 int ret = -1;
0x00000000004004e2 <func+8>: movl $0xffffffff,-0x4(%rbp)
20
21 ret = sum(11, 22, 33, 44, 55, 66, 77, 88, 99);
0x00000000004004e9 <func+15>: movl $0x63,0x10(%rsp) <== 第9引数(0x63 = 99) を スタック+0x10 の位置に格納
0x00000000004004f1 <func+23>: movl $0x58,0x8(%rsp) <== 第8引数(0x58 = 88) を スタック+0x8 の位置に格納
0x00000000004004f9 <func+31>: movl $0x4d,(%rsp) <== 第7引数(0x4d = 77) を スタック の位置に格納
0x0000000000400500 <func+38>: mov $0x42,%r9d <== 第6引数(0x42 = 66) を R9 に格納
0x0000000000400506 <func+44>: mov $0x37,%r8d <== 第5引数(0x37 = 55) を R8 に格納
0x000000000040050c <func+50>: mov $0x2c,%ecx <== 第4引数(0x2c = 44) を ECX に格納
0x0000000000400511 <func+55>: mov $0x21,%edx <== 第3引数(0x21 = 33) を EDX に格納
0x0000000000400516 <func+60>: mov $0x16,%esi <== 第2引数(0x16 = 22) を RSI に格納
0x000000000040051b <func+65>: mov $0xb,%edi <== 第1引数( 0xb = 11) を RDI に格納
0x0000000000400520 <func+70>: callq 0x400498 <sum> <== 関数 sum を呼出
0x0000000000400525 <func+75>: mov %eax,-0x4(%rbp) <== 戻り値を RAX から受け取る
22
23 printf("sum: %d\n", ret);
0x0000000000400528 <func+78>: mov -0x4(%rbp),%esi
0x000000000040052b <func+81>: mov $0x400658,%edi
0x0000000000400530 <func+86>: mov $0x0,%eax
0x0000000000400535 <func+91>: callq 0x400398 <printf@plt>
24 }
0x000000000040053a <func+96>: leaveq
0x000000000040053b <func+97>: retq
End of assembler dump.
確かに呼出規約通り、第1引数~第6引数まで RDI, RSI, RDX, RCX, R8, R9 を順に使用して、 残りの第7引数~第9引数はスタックを利用している。戻り値は RAX に格納されている。
ちなみに呼び出される関数 sum の disas の結果は以下
(gdb) disas/m sum
Dump of assembler code for function sum:
9 {
0x0000000000400498 <sum+0>: push %rbp
0x0000000000400499 <sum+1>: mov %rsp,%rbp
0x000000000040049c <sum+4>: mov %edi,-0x14(%rbp)
0x000000000040049f <sum+7>: mov %esi,-0x18(%rbp)
0x00000000004004a2 <sum+10>: mov %edx,-0x1c(%rbp)
0x00000000004004a5 <sum+13>: mov %ecx,-0x20(%rbp)
0x00000000004004a8 <sum+16>: mov %r8d,-0x24(%rbp)
0x00000000004004ac <sum+20>: mov %r9d,-0x28(%rbp)
10 int s = -9999;
0x00000000004004b0 <sum+24>: movl $0xffffd8f1,-0x4(%rbp)
11
12 s = a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9;
0x00000000004004b7 <sum+31>: mov -0x18(%rbp),%eax
0x00000000004004ba <sum+34>: add -0x14(%rbp),%eax
0x00000000004004bd <sum+37>: add -0x1c(%rbp),%eax
0x00000000004004c0 <sum+40>: add -0x20(%rbp),%eax
0x00000000004004c3 <sum+43>: add -0x24(%rbp),%eax
0x00000000004004c6 <sum+46>: add -0x28(%rbp),%eax
0x00000000004004c9 <sum+49>: add 0x10(%rbp),%eax
0x00000000004004cc <sum+52>: add 0x18(%rbp),%eax
0x00000000004004cf <sum+55>: add 0x20(%rbp),%eax
0x00000000004004d2 <sum+58>: mov %eax,-0x4(%rbp)
13
14 return s;
0x00000000004004d5 <sum+61>: mov -0x4(%rbp),%eax
15 }
0x00000000004004d8 <sum+64>: leaveq
0x00000000004004d9 <sum+65>: retq
End of assembler dump.
関数 sum で break させて、レジスタの状態やスタックを確認すれば引数の値が分かる。
(gdb) b sum
Breakpoint 1 at 0x4004b0: file sample1.c, line 10.
(gdb) run
Starting program: /home/hashi/tmp/sample1
Breakpoint 1, sum (a1=11, a2=22, a3=33, a4=44, a5=55, a6=66, a7=77, a8=88, a9=99) at sample1.c:10
10 int s = -9999;
(gdb) info reg
rax 0x0 0
rbx 0x3e1c81bbc0 266766236608
rcx 0x2c 44 <== 第4引数
rdx 0x21 33 <== 第3引数
rsi 0x16 22 <== 第2引数
rdi 0xb 11 <== 第1引数
rbp 0x7fffffffe270 0x7fffffffe270
rsp 0x7fffffffe270 0x7fffffffe270
r8 0x37 55 <== 第5引数
r9 0x42 66 <== 第6引数
r10 0x0 0
r11 0x3e1ca1d8a0 266768341152
r12 0x0 0
r13 0x7fffffffe3b0 140737488348080
r14 0x0 0
r15 0x0 0
rip 0x4004b0 0x4004b0 <sum+24> <== sum+20 までの処理が終わっていて次の処理は sum+24
eflags 0x202 [ IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
fctrl 0x37f 895
fstat 0x0 0
ftag 0xffff 65535
fiseg 0x0 0
fioff 0x0 0
foseg 0x0 0
fooff 0x0 0
fop 0x0 0
mxcsr 0x1f80 [ IM DM ZM OM UM PM ]
(gdb) x/6gx $rbp
0x7fffffffe270: 0x00007fffffffe2b0 0x0000000000400525
0x7fffffffe280: 0x000000000000004d 0x0000000000000058
~~~~~~~~第7引数 ~~~~~~~~第8引数
0x7fffffffe290: 0x2cb4304900000063 0x00000000004005a7
~~~~~~~~第9引数
スタック上の値を確認するのに x/6gx $rbp
としているのは、関数で break したときは関数の最初の方の処理(サブルーチンプロローグ)まで
終わっているが、普通はここまでで引数をスタックに積んだ後、
call <function>
: スタックに戻り先の命令アドレスを積んで関数を呼ぶ(rsp が -0x8 される)push %rbp
: ベースポインタ(rbp)の値が push され、スタックに保存される(rsp が -0x8 される)mov %rsp,%rbp
: スタックポインタ(rsp)の値をベースポインタ(rbp)に保存するsub $0xXX,%rsp
: ローカル変数を保持するためにスタックを上げる)という処理が一般的に行われるためである。結果としてスタックに積まれた引数は $rbp + 0x10
の位置から、第7引数が $rbp + 0x10
, 第8引数が $rbp + 0x18
, 第9引数が $rbp + 0x20
,… というふうに存在することになる。
関数 sum から返った後に レジスタ RAX にて戻り値が分かる。
(gdb) finish
Run till exit from #0 sum (a1=11, a2=22, a3=33, a4=44, a5=55, a6=66, a7=77, a8=88, a9=99) at sample1.c:12
0x0000000000400525 in func () at sample1.c:21
21 ret = sum(11, 22, 33, 44, 55, 66, 77, 88, 99);
Value returned is $1 = 495
(gdb) p $rax
$2 = 495
せっかくなので、関数 func から関数 sum を呼び出しているところを逆アセンブルの結果から詳しく追ってみる。
初期状態(関数 func が呼ばれる直前)
rbp 0x7fffffffe2d0 0x7fffffffe2d0
rsp 0x7fffffffe2c0 0x7fffffffe2c0
0x7fffffffe2b8 +------------------+
| |
rsp --> 0x7fffffffe2c0 +------------------+
| |
0x7fffffffe2c8 +------------------+
| |
rbp --> 0x7fffffffe2d0 +------------------+
| |
0x7fffffffe2d8 +------------------+
関数 main から関数 func を呼ぶ
0x0000000000400550 <main+20>: callq 0x4004da <func> <== 関数 func を呼出
この命令後のレジスタの状態
rbp 0x7fffffffe2d0 0x7fffffffe2d0
rsp 0x7fffffffe2b8 0x7fffffffe2b8
rip 0x4004da 0x4004da <func>
rsp --> 0x7fffffffe2b8 +------------------+
|0x0000000000400555| <== 関数 main の戻り先命令アドレス
0x7fffffffe2c0 +------------------+
| |
0x7fffffffe2c8 +------------------+
| |
rbp --> 0x7fffffffe2d0 +------------------+
| |
0x7fffffffe2d8 +------------------+
関数 func が呼び出され、ここから関数 func に入る。
Dump of assembler code for function func:
18 {
0x00000000004004da <func+0>: push %rbp
0x00000000004004db <func+1>: mov %rsp,%rbp
0x00000000004004de <func+4>: sub $0x30,%rsp
この命令後のレジスタの状態
rbp 0x7fffffffe2b0 0x7fffffffe2b0
rsp 0x7fffffffe280 0x7fffffffe280
rsp --> 0x7fffffffe280 +------------------+ <== ローカル変数を保持するためにスタックを上げる
| |
0x7fffffffe288 +------------------+
| |
0x7fffffffe290 +------------------+
| |
0x7fffffffe298 +------------------+
| |
0x7fffffffe2a0 +------------------+
| |
0x7fffffffe2a8 +------------------+
| |
rbp --> 0x7fffffffe2b0 +------------------+ <== 元のスタックの位置がベースポインタに保存される
|0x00007fffffffe2d0| <== ベースポインタの値がスタックに保存される
0x7fffffffe2b8 +------------------+
|0x0000000000400555|
0x7fffffffe2c0 +------------------+
| |
0x7fffffffe2c8 +------------------+
| |
0x7fffffffe2d0 +------------------+
| |
0x7fffffffe2d8 +------------------+
進める
19 int ret = -1;
0x00000000004004e2 <func+8>: movl $0xffffffff,-0x4(%rbp)
この命令後のレジスタの状態
rbp 0x7fffffffe2b0 0x7fffffffe2b0
rsp 0x7fffffffe280 0x7fffffffe280
rsp --> 0x7fffffffe280 +------------------+
| |
0x7fffffffe288 +------------------+
| |
0x7fffffffe290 +------------------+
| |
0x7fffffffe298 +------------------+
| |
0x7fffffffe2a0 +------------------+
| |
0x7fffffffe2a8 +------------------+
| 0xffffffff|
rbp --> 0x7fffffffe2b0 +------------------+
|0x00007fffffffe2d0|
0x7fffffffe2b8 +------------------+
|0x0000000000400555|
0x7fffffffe2c0 +------------------+
| |
0x7fffffffe2c8 +------------------+
| |
0x7fffffffe2d0 +------------------+
| |
0x7fffffffe2d8 +------------------+
進める
20
21 ret = sum(11, 22, 33, 44, 55, 66, 77, 88, 99);
0x00000000004004e9 <func+15>: movl $0x63,0x10(%rsp) <== 第9引数(0x63 = 99) を スタック+0x10 の位置に格納
0x00000000004004f1 <func+23>: movl $0x58,0x8(%rsp) <== 第8引数(0x58 = 88) を スタック+0x8 の位置に格納
0x00000000004004f9 <func+31>: movl $0x4d,(%rsp) <== 第7引数(0x4d = 77) を スタック の位置に格納
0x0000000000400500 <func+38>: mov $0x42,%r9d <== 第6引数(0x42 = 66) を R9 に格納
0x0000000000400506 <func+44>: mov $0x37,%r8d <== 第5引数(0x37 = 55) を R8 に格納
0x000000000040050c <func+50>: mov $0x2c,%ecx <== 第4引数(0x2c = 44) を ECX に格納
0x0000000000400511 <func+55>: mov $0x21,%edx <== 第3引数(0x21 = 33) を EDX に格納
0x0000000000400516 <func+60>: mov $0x16,%esi <== 第2引数(0x16 = 22) を RSI に格納
0x000000000040051b <func+65>: mov $0xb,%edi <== 第1引数( 0xb = 11) を RDI に格納
この命令後のレジスタの状態
rcx 0x2c 44 <== 第4引数
rdx 0x21 33 <== 第3引数
rsi 0x16 22 <== 第2引数
rdi 0xb 11 <== 第1引数
rbp 0x7fffffffe2b0 0x7fffffffe2b0
rsp 0x7fffffffe280 0x7fffffffe280
r8 0x37 55 <== 第5引数
r9 0x42 66 <== 第6引数
rsp --> 0x7fffffffe280 +------------------+
|0x0000004d | <== 第7引数
0x7fffffffe288 +------------------+
|0x00000058 | <== 第8引数
0x7fffffffe290 +------------------+
|0x00000063 | <== 第9引数
0x7fffffffe298 +------------------+
| |
0x7fffffffe2a0 +------------------+
| |
0x7fffffffe2a8 +------------------+
| 0xffffffff|
rbp --> 0x7fffffffe2b0 +------------------+
|0x00007fffffffe2d0|
0x7fffffffe2b8 +------------------+
|0x0000000000400555|
0x7fffffffe2c0 +------------------+
| |
0x7fffffffe2c8 +------------------+
| |
0x7fffffffe2d0 +------------------+
| |
0x7fffffffe2d8 +------------------+
進める
0x0000000000400520 <func+70>: callq 0x400498 <sum> <== 関数 sum を呼出
この命令後のレジスタの状態
rcx 0x2c 44
rdx 0x21 33
rsi 0x16 22
rdi 0xb 11
rbp 0x7fffffffe2b0 0x7fffffffe2b0
rsp 0x7fffffffe278 0x7fffffffe278
r8 0x37 55
r9 0x42 66
rip 0x400498 0x400498 <sum> <== 次は関数 sum の命令を呼ぶ
rsp --> 0x7fffffffe278 +------------------+
|0x0000000000400525| <== 関数 func の戻り先命令アドレス
0x7fffffffe280 +------------------+
| 0x0000004d |
0x7fffffffe288 +------------------+
| 0x00000058 |
0x7fffffffe290 +------------------+
| 0x00000063 |
0x7fffffffe298 +------------------+
| |
0x7fffffffe2a0 +------------------+
| |
0x7fffffffe2a8 +------------------+
| 0xffffffff |
rbp --> 0x7fffffffe2b0 +------------------+
|0x00007fffffffe2d0|
0x7fffffffe2b8 +------------------+
|0x0000000000400555|
0x7fffffffe2c0 +------------------+
| |
0x7fffffffe2c8 +------------------+
| |
0x7fffffffe2d0 +------------------+
| |
0x7fffffffe2d8 +------------------+
ここから関数 sum に入る
Dump of assembler code for function sum:
9 {
0x0000000000400498 <sum+0>: push %rbp
0x0000000000400499 <sum+1>: mov %rsp,%rbp
0x000000000040049c <sum+4>: mov %edi,-0x14(%rbp)
0x000000000040049f <sum+7>: mov %esi,-0x18(%rbp)
0x00000000004004a2 <sum+10>: mov %edx,-0x1c(%rbp)
0x00000000004004a5 <sum+13>: mov %ecx,-0x20(%rbp)
0x00000000004004a8 <sum+16>: mov %r8d,-0x24(%rbp)
0x00000000004004ac <sum+20>: mov %r9d,-0x28(%rbp)
この命令後のレジスタの状態
rcx 0x2c 44
rdx 0x21 33
rsi 0x16 22
rdi 0xb 11
rbp 0x7fffffffe270 0x7fffffffe270
rsp 0x7fffffffe270 0x7fffffffe270
r8 0x37 55
r9 0x42 66
0x7fffffffe248 +------------------+
|0x00000042 0x00000037| <== 66 55
0x7fffffffe250 +------------------+
|0x0000002c 0x00000021| <== 44 33
0x7fffffffe258 +------------------+
|0x00000016 0x0000000b| <== 22 11
0x7fffffffe260 +------------------+
| |
0x7fffffffe268 +------------------+
| |
rsp +-->0x7fffffffe270 +------------------+ <== 元のスタックの位置がベースポインタに保存される
rbp + |0x00007fffffffe2b0| <== ベースポインタの値がスタックに保存される
0x7fffffffe278 +------------------+
|0x0000000000400525|
0x7fffffffe280 +------------------+
|0x0000004d |
0x7fffffffe288 +------------------+
|0x00000058 |
0x7fffffffe290 +------------------+
|0x00000063 |
0x7fffffffe298 +------------------+
| |
0x7fffffffe2a0 +------------------+
| |
0x7fffffffe2a8 +------------------+
| 0xffffffff|
0x7fffffffe2b0 +------------------+
|0x00007fffffffe2d0|
0x7fffffffe2b8 +------------------+
|0x0000000000400555|
0x7fffffffe2c0 +------------------+
| |
0x7fffffffe2c8 +------------------+
| |
0x7fffffffe2d0 +------------------+
| |
0x7fffffffe2d8 +------------------+
進める
10 int s = -9999;
0x00000000004004b0 <sum+24>: movl $0xffffd8f1,-0x4(%rbp)
この命令後のレジスタの状態
rcx 0x2c 44
rdx 0x21 33
rsi 0x16 22
rdi 0xb 11
rbp 0x7fffffffe270 0x7fffffffe270
rsp 0x7fffffffe270 0x7fffffffe270
r8 0x37 55
r9 0x42 66
0x7fffffffe248 +------------------+
|0x00000042 0x00000037|
0x7fffffffe250 +------------------+
|0x0000002c 0x00000021|
0x7fffffffe258 +------------------+
|0x00000016 0x0000000b|
0x7fffffffe260 +------------------+
| |
0x7fffffffe268 +------------------+
| 0xffffd8f1| <== -9999
rsp +-->0x7fffffffe270 +------------------+
rbp + |0x00007fffffffe2b0|
0x7fffffffe278 +------------------+
|0x0000000000400525|
0x7fffffffe280 +------------------+
|0x0000004d |
0x7fffffffe288 +------------------+
|0x00000058 |
0x7fffffffe290 +------------------+
|0x00000063 |
0x7fffffffe298 +------------------+
| |
0x7fffffffe2a0 +------------------+
| |
0x7fffffffe2a8 +------------------+
| 0xffffffff|
0x7fffffffe2b0 +------------------+
|0x00007fffffffe2d0|
0x7fffffffe2b8 +------------------+
|0x0000000000400555|
0x7fffffffe2c0 +------------------+
| |
0x7fffffffe2c8 +------------------+
| |
0x7fffffffe2d0 +------------------+
| |
0x7fffffffe2d8 +------------------+
進める
11
12 s = a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9;
0x00000000004004b7 <sum+31>: mov -0x18(%rbp),%eax
0x00000000004004ba <sum+34>: add -0x14(%rbp),%eax
0x00000000004004bd <sum+37>: add -0x1c(%rbp),%eax
0x00000000004004c0 <sum+40>: add -0x20(%rbp),%eax
0x00000000004004c3 <sum+43>: add -0x24(%rbp),%eax
0x00000000004004c6 <sum+46>: add -0x28(%rbp),%eax
0x00000000004004c9 <sum+49>: add 0x10(%rbp),%eax
0x00000000004004cc <sum+52>: add 0x18(%rbp),%eax
0x00000000004004cf <sum+55>: add 0x20(%rbp),%eax
0x00000000004004d2 <sum+58>: mov %eax,-0x4(%rbp)
この命令後のレジスタの状態
rax 0x1ef 495
rcx 0x2c 44
rdx 0x21 33
rsi 0x16 22
rdi 0xb 11
rbp 0x7fffffffe270 0x7fffffffe270
rsp 0x7fffffffe270 0x7fffffffe270
r8 0x37 55
r9 0x42 66
0x7fffffffe248 +------------------+
|0x00000042 0x00000037|
0x7fffffffe250 +------------------+
|0x0000002c 0x00000021|
0x7fffffffe258 +------------------+
|0x00000016 0x0000000b|
0x7fffffffe260 +------------------+
| |
0x7fffffffe268 +------------------+
| 0x000001ef| <== 495
rsp +-->0x7fffffffe270 +------------------+
rbp + |0x00007fffffffe2b0|
0x7fffffffe278 +------------------+
|0x0000000000400525|
0x7fffffffe280 +------------------+
|0x0000004d |
0x7fffffffe288 +------------------+
|0x00000058 |
0x7fffffffe290 +------------------+
|0x00000063 |
0x7fffffffe298 +------------------+
| |
0x7fffffffe2a0 +------------------+
| |
0x7fffffffe2a8 +------------------+
| 0xffffffff|
0x7fffffffe2b0 +------------------+
|0x00007fffffffe2d0|
0x7fffffffe2b8 +------------------+
|0x0000000000400555|
0x7fffffffe2c0 +------------------+
| |
0x7fffffffe2c8 +------------------+
| |
0x7fffffffe2d0 +------------------+
| |
0x7fffffffe2d8 +------------------+
進める
13
14 return s;
0x00000000004004d5 <sum+61>: mov -0x4(%rbp),%eax
この命令後のレジスタの状態
rax 0x1ef 495 <== 戻り値が RAX に入っている
rcx 0x2c 44
rdx 0x21 33
rsi 0x16 22
rdi 0xb 11
rbp 0x7fffffffe270 0x7fffffffe270
rsp 0x7fffffffe270 0x7fffffffe270
r8 0x37 55
r9 0x42 66
0x7fffffffe248 +------------------+
|0x00000042 0x00000037|
0x7fffffffe250 +------------------+
|0x0000002c 0x00000021|
0x7fffffffe258 +------------------+
|0x00000016 0x0000000b|
0x7fffffffe260 +------------------+
| |
0x7fffffffe268 +------------------+
| 0x000001ef|
rsp +-->0x7fffffffe270 +------------------+
rbp + |0x00007fffffffe2b0|
0x7fffffffe278 +------------------+
|0x0000000000400525|
0x7fffffffe280 +------------------+
|0x0000004d |
0x7fffffffe288 +------------------+
|0x00000058 |
0x7fffffffe290 +------------------+
|0x00000063 |
0x7fffffffe298 +------------------+
| |
0x7fffffffe2a0 +------------------+
| |
0x7fffffffe2a8 +------------------+
| 0xffffffff|
0x7fffffffe2b0 +------------------+
|0x00007fffffffe2d0|
0x7fffffffe2b8 +------------------+
|0x0000000000400555|
0x7fffffffe2c0 +------------------+
| |
0x7fffffffe2c8 +------------------+
| |
0x7fffffffe2d0 +------------------+
| |
0x7fffffffe2d8 +------------------+
進める
15 }
0x00000000004004d8 <sum+64>: leaveq
この命令後のレジスタの状態
rbp 0x7fffffffe2b0 0x7fffffffe2b0
rsp 0x7fffffffe278 0x7fffffffe278
0x7fffffffe248 +------------------+
|0x00000042 0x00000037|
0x7fffffffe250 +------------------+
|0x0000002c 0x00000021|
0x7fffffffe258 +------------------+
|0x00000016 0x0000000b|
0x7fffffffe260 +------------------+
| |
0x7fffffffe268 +------------------+
| 0x000001ef|
0x7fffffffe270 +------------------+
|0x00007fffffffe2b0|
rsp --->0x7fffffffe278 +------------------+
|0x0000000000400525|
0x7fffffffe280 +------------------+
|0x0000004d |
0x7fffffffe288 +------------------+
|0x00000058 |
0x7fffffffe290 +------------------+
|0x00000063 |
0x7fffffffe298 +------------------+
| |
0x7fffffffe2a0 +------------------+
| |
0x7fffffffe2a8 +------------------+
| 0xffffffff|
rbp --->0x7fffffffe2b0 +------------------+ <== ベースポインタが関数 sum 呼出直前に戻る
|0x00007fffffffe2d0|
0x7fffffffe2b8 +------------------+
|0x0000000000400555|
0x7fffffffe2c0 +------------------+
| |
0x7fffffffe2c8 +------------------+
| |
0x7fffffffe2d0 +------------------+
| |
0x7fffffffe2d8 +------------------+
進める
0x00000000004004d9 <sum+65>: retq
この命令後のレジスタの状態
rbp 0x7fffffffe2b0 0x7fffffffe2b0
rsp 0x7fffffffe280 0x7fffffffe280
rip 0x400525 0x400525 <func+75> <== 関数 func の戻り先アドレス
0x7fffffffe248 +------------------+
|0x00000042 0x00000037|
0x7fffffffe250 +------------------+
|0x0000002c 0x00000021|
0x7fffffffe258 +------------------+
|0x00000016 0x0000000b|
0x7fffffffe260 +------------------+
| |
0x7fffffffe268 +------------------+
| 0x000001ef|
0x7fffffffe270 +------------------+
|0x00007fffffffe2b0|
0x7fffffffe278 +------------------+
|0x0000000000400525|
rsp --> 0x7fffffffe280 +------------------+ <== スタックポインタが関数 sum 呼出直前に戻る
|0x0000004d |
0x7fffffffe288 +------------------+
|0x00000058 |
0x7fffffffe290 +------------------+
|0x00000063 |
0x7fffffffe298 +------------------+
| |
0x7fffffffe2a0 +------------------+
| |
0x7fffffffe2a8 +------------------+
| 0xffffffff|
rbp --->0x7fffffffe2b0 +------------------+
|0x00007fffffffe2d0|
0x7fffffffe2b8 +------------------+
|0x0000000000400555|
0x7fffffffe2c0 +------------------+
| |
0x7fffffffe2c8 +------------------+
| |
0x7fffffffe2d0 +------------------+
| |
0x7fffffffe2d8 +------------------+
関数 func に戻ってきた
0x0000000000400525 <func+75>: mov %eax,-0x4(%rbp) <== 戻り値を RAX から受け取る
この命令後のレジスタの状態
rax 0x1ef 495
rbp 0x7fffffffe2b0 0x7fffffffe2b0
rsp 0x7fffffffe280 0x7fffffffe280
0x7fffffffe248 +------------------+
|0x00000042 0x00000037|
0x7fffffffe250 +------------------+
|0x0000002c 0x00000021|
0x7fffffffe258 +------------------+
|0x00000016 0x0000000b|
0x7fffffffe260 +------------------+
| |
0x7fffffffe268 +------------------+
| 0x000001ef|
0x7fffffffe270 +------------------+
|0x00007fffffffe2b0|
0x7fffffffe278 +------------------+
|0x0000000000400525|
rsp --> 0x7fffffffe280 +------------------+
|0x0000004d |
0x7fffffffe288 +------------------+
|0x00000058 |
0x7fffffffe290 +------------------+
|0x00000063 |
0x7fffffffe298 +------------------+
| |
0x7fffffffe2a0 +------------------+
| |
0x7fffffffe2a8 +------------------+
| 0x000001ef| <== 戻り値を RAX から受け取って格納された
rbp --->0x7fffffffe2b0 +------------------+
|0x00007fffffffe2d0|
0x7fffffffe2b8 +------------------+
|0x0000000000400555|
0x7fffffffe2c0 +------------------+
| |
0x7fffffffe2c8 +------------------+
| |
0x7fffffffe2d0 +------------------+
| |
0x7fffffffe2d8 +------------------+
進める
22
23 printf("sum: %d\n", ret);
0x0000000000400528 <func+78>: mov -0x4(%rbp),%esi
0x000000000040052b <func+81>: mov $0x400658,%edi
0x0000000000400530 <func+86>: mov $0x0,%eax
0x0000000000400535 <func+91>: callq 0x400398 <printf@plt>
この命令後のレジスタの状態(printf 関数から返った直後)
rax 0x9 9 <=== printf からの戻り値(出力バイト数なので9バイト, "sum: 495\n")
rsi 0x2aaaaaaac000 46912496123904 <== printf の中で使用されたので変わっている
rdi 0x1 1 <== printf の中で使用されたので変わっている
rbp 0x7fffffffe2b0 0x7fffffffe2b0
rsp 0x7fffffffe280 0x7fffffffe280
rsp --> 0x7fffffffe280 +------------------+ <== スタックポインタより上位アドレス(この絵では↓)は printf 呼出前と同じ
|0x0000004d |
0x7fffffffe288 +------------------+
|0x00000058 |
0x7fffffffe290 +------------------+
|0x00000063 |
0x7fffffffe298 +------------------+
| |
0x7fffffffe2a0 +------------------+
| |
0x7fffffffe2a8 +------------------+
| 0x000001ef|
rbp --->0x7fffffffe2b0 +------------------+
|0x00007fffffffe2d0|
0x7fffffffe2b8 +------------------+
|0x0000000000400555|
0x7fffffffe2c0 +------------------+
| |
0x7fffffffe2c8 +------------------+
| |
0x7fffffffe2d0 +------------------+
| |
0x7fffffffe2d8 +------------------+
進める
24 }
0x000000000040053a <func+96>: leaveq
0x000000000040053b <func+97>: retq
この命令後のレジスタの状態
rbp 0x7fffffffe2d0 0x7fffffffe2d0 <== 関数 func 呼出直前に戻っている
rsp 0x7fffffffe2c0 0x7fffffffe2c0 <== 関数 func 呼出直前に戻っている
rip 0x400555 0x400555 <main+25> <== 関数 main の戻り先アドレス
0x7fffffffe280 +------------------+
|0x0000004d |
0x7fffffffe288 +------------------+
|0x00000058 |
0x7fffffffe290 +------------------+
|0x00000063 |
0x7fffffffe298 +------------------+
| |
0x7fffffffe2a0 +------------------+
| |
0x7fffffffe2a8 +------------------+
| 0x000001ef|
0x7fffffffe2b0 +------------------+
|0x00007fffffffe2d0|
0x7fffffffe2b8 +------------------+
|0x0000000000400555|
rsp --> 0x7fffffffe2c0 +------------------+
| |
0x7fffffffe2c8 +------------------+
| |
rbp --> 0x7fffffffe2d0 +------------------+
| |
0x7fffffffe2d8 +------------------+
OS コマンドによるボトルネック調査方法をまとめる。
CPU 使用率を確認する。使用率が 100% に近くなっている(= idle が 0% に近くなっている)とボトルネック。
top
top
では複数の論理 CPU がある場合もサーバ全体として 1 つに集約されて出力される。
$ top
top - 07:23:36 up 45 days, 17:41, 2 users, load average: 7.22, 9.43, 8.03
Tasks: 223 total, 1 running, 222 sleeping, 0 stopped, 0 zombie
Cpu(s): 7.1%us, 8.0%sy, 0.0%ni, 70.8%id, 7.1%wa, 2.7%hi, 4.4%si, 0.0%st
Mem: 4044532k total, 3735152k used, 309380k free, 180688k buffers
Swap: 8159224k total, 629880k used, 7529344k free, 1822664k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1314 oracle 15 0 1800m 83m 63m S 1.3 2.1 16:38.42 oracle
32392 root 15 0 362m 39m 15m S 1.0 1.0 18:24.31 orarootagent.bi
667 grid 15 0 1244m 41m 16m S 0.7 1.0 11:23.32 oraagent.bin
32450 grid RT 0 334m 121m 53m S 0.7 3.1 28:04.95 ocssd.bin
442 grid 15 0 729m 32m 19m S 0.3 0.8 5:04.54 oracle
676 root 15 0 1681m 27m 13m S 0.3 0.7 22:21.63 orarootagent.bi
1016 oracle 15 0 826m 36m 16m S 0.3 0.9 7:57.93 oraagent.bin
1300 oracle -2 0 1781m 16m 14m S 0.3 0.4 0:28.27 oracle
1306 oracle 15 0 1787m 24m 17m S 0.3 0.6 0:39.76 oracle
Cpu(s):
で始まる行が CPU 使用率
Cpu(s): 7.1%us, 8.0%sy, 0.0%ni, 70.8%id, 7.1%wa, 2.7%hi, 4.4%si, 0.0%st
(-b オプションによるバッチモードでなく)対話的に起動した場合は 1
を押すと個々の CPU 毎の
CPU 使用率が出力される。
$ top # 起動後 1 を押下
top - 07:25:32 up 45 days, 17:43, 2 users, load average: 5.02, 8.37, 7.84
Tasks: 223 total, 1 running, 222 sleeping, 0 stopped, 0 zombie
Cpu0 : 5.6%us, 16.7%sy, 0.0%ni, 33.3%id, 41.7%wa, 0.0%hi, 2.8%si, 0.0%st
Cpu1 : 8.1%us, 5.4%sy, 0.0%ni, 70.3%id, 13.5%wa, 0.0%hi, 2.7%si, 0.0%st
Mem: 4044532k total, 3735368k used, 309164k free, 180704k buffers
Swap: 8159224k total, 629880k used, 7529344k free, 1822916k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
32392 root 15 0 362m 39m 15m S 1.0 1.0 18:24.71 orarootagent.bi
32450 grid RT 0 334m 121m 53m S 1.0 3.1 28:05.51 ocssd.bin
676 root 15 0 1681m 27m 13m S 0.7 0.7 22:22.10 orarootagent.bi
1323 oracle -2 0 1795m 280m 265m S 0.7 7.1 10:14.08 oracle
32382 grid 15 0 172m 25m 11m S 0.7 0.6 1:30.89 gpnpd.bin
442 grid 15 0 729m 32m 19m S 0.3 0.8 5:04.61 oracle
477 grid 15 0 718m 21m 16m S 0.3 0.6 1:58.51 oracle
667 grid 15 0 1244m 41m 16m S 0.3 1.0 11:23.55 oraagent.bin
1016 oracle 15 0 826m 36m 16m S 0.3 0.9 7:58.08 oraagent.bin
本環境は CPU 数が2つのため、以下のようにそれぞれの CPU 使用率が出力されている。
Cpu0 : 5.6%us, 16.7%sy, 0.0%ni, 33.3%id, 41.7%wa, 0.0%hi, 2.8%si, 0.0%st
Cpu1 : 8.1%us, 5.4%sy, 0.0%ni, 70.3%id, 13.5%wa, 0.0%hi, 2.7%si, 0.0%st
mpstat
mpstat
でも CPU 使用率が確認できる。デフォルトではすべての CPU が集約されて出力される。
$ mpstat 2 3 # 2秒毎に3回出力
Linux 2.6.18-194.el5 (sv1.local) 04/08/13
07:33:04 CPU %user %nice %sys %iowait %irq %soft %steal %idle intr/s
07:33:06 all 4.41 0.00 4.41 13.24 1.47 0.00 0.00 76.47 1409.09
07:33:08 all 5.06 0.00 3.80 6.33 0.00 2.53 0.00 82.28 1481.40
07:33:10 all 10.61 0.00 10.61 12.12 0.00 3.03 0.00 63.64 1468.75
Average: all 6.57 0.00 6.10 10.33 0.47 1.88 0.00 74.65 1455.56
個々の CPU の使用率を確認したい場合は、-P ALL
オプションを付与する。
$ mpstat -P ALL 2 3
Linux 2.6.18-194.el5 (sv1.local) 04/08/13
07:35:03 CPU %user %nice %sys %iowait %irq %soft %steal %idle intr/s
07:35:05 all 9.09 0.00 5.19 7.79 0.00 2.60 0.00 75.32 1353.85
07:35:05 0 10.26 0.00 5.13 15.38 2.56 2.56 0.00 64.10 1353.85
07:35:05 1 7.69 0.00 2.56 2.56 0.00 2.56 0.00 84.62 0.00
07:35:05 CPU %user %nice %sys %iowait %irq %soft %steal %idle intr/s
07:35:07 all 5.80 0.00 4.35 10.14 0.00 2.90 0.00 76.81 1412.12
07:35:07 0 3.03 0.00 6.06 12.12 0.00 3.03 0.00 75.76 1412.12
07:35:07 1 5.88 0.00 2.94 8.82 0.00 0.00 0.00 82.35 0.00
07:35:07 CPU %user %nice %sys %iowait %irq %soft %steal %idle intr/s
07:35:09 all 10.00 0.00 15.00 13.33 0.00 1.67 0.00 60.00 1393.55
07:35:09 0 6.45 0.00 19.35 25.81 0.00 3.23 0.00 45.16 1393.55
07:35:09 1 13.33 0.00 13.33 0.00 0.00 0.00 0.00 73.33 0.00
Average: CPU %user %nice %sys %iowait %irq %soft %steal %idle intr/s
Average: all 8.25 0.00 7.77 10.19 0.00 2.43 0.00 71.36 1384.47
Average: 0 6.80 0.00 9.71 17.48 0.97 2.91 0.00 62.14 1384.47
Average: 1 8.74 0.00 5.83 3.88 0.00 0.97 0.00 80.58 0.00
各項目の意味は以下の通り。
項目 | 説明 | |
---|---|---|
CPU | CPU番号。ALLの場合は、全CPUの平均値であることを示す。 | |
%user | ユーザレベル(アプリケーション)のCPU使用率 | |
%nice | 優先度(ナイス値)によるユーザーレベルのCPU使用率 | |
%sys | システムレベル(kernel)のCPU使用率 | |
%iowait | ディスクi/o競合によるCPU待機時間割合 | |
%irq | CPUの割り込み実行時間割合 | |
%soft | CPUのソフトウェア割り込み実行時間割合 | |
%idle | CPUのアイドル時間割合(ディスクi/o待機時間はのぞく) | |
intr/s | 1秒あたりの平均割り込み数 |
vmstat
vmstat
からも CPU 使用率が確認できる。
$ vmstat 2 3
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 629504 306172 181788 1825200 0 1 105 236 14 12 4 5 85 6 0
10 1 629504 306048 181788 1825200 0 0 35 732 300 1425 7 5 77 11 0
4 0 629504 306040 181788 1825204 0 0 27 2 236 1552 9 11 75 5 0
項目 | 説明 | |
---|---|---|
r | CPUを割り当て中もしくは割り当て可能なプロセスの数。CPUの個数以下であることが望ましい。 | |
b | 割り込みを禁止しているプロセスの数。I/O待ちなどで割り込み不可能なときに発生。ゼロであることが望ましい。 | |
us | ユーザー時間の CPU 使用率(nice 時間を含む) | |
sy | システム時間の CPU 使用率 | |
id | アイドル時間の割合 | |
wa | IO 待ち時間の割合 |
CPU の割り当て状況を示す r, b の値も重要。r が CPU の個数と同じ場合は、システムの CPU がフルで使われており、 r が CPU 数より多い場合は、CPU の割り当てを待っていて、ボトルネックとなっている状況。 また、b が 0 より大きい場合は、I/O 等 CPU 以外のボトルネックが発生している可能性がある。
プロセス単位の CPU 使用率を確認し、CPU 使用率が 100% に近くなっているプロセスが 無いか確認する。
top
top
によりプロセス単位の CPU 使用率が確認できる。
出力結果の下部にプロセス毎の情報があり、%CPU
で CPU 使用率が確認できる。
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1314 oracle 15 0 1800m 83m 63m S 1.3 2.1 16:38.42 oracle
32392 root 15 0 362m 39m 15m S 1.0 1.0 18:24.31 orarootagent.bi
667 grid 15 0 1244m 41m 16m S 0.7 1.0 11:23.32 oraagent.bin
32450 grid RT 0 334m 121m 53m S 0.7 3.1 28:04.95 ocssd.bin
442 grid 15 0 729m 32m 19m S 0.3 0.8 5:04.54 oracle
676 root 15 0 1681m 27m 13m S 0.3 0.7 22:21.63 orarootagent.bi
1016 oracle 15 0 826m 36m 16m S 0.3 0.9 7:57.93 oraagent.bin
1300 oracle -2 0 1781m 16m 14m S 0.3 0.4 0:28.27 oracle
1306 oracle 15 0 1787m 24m 17m S 0.3 0.6 0:39.76 oracle
top
を対話的に起動すると画面サイズ分しかプロセスが出力されず、すべてのプロセス
が確認できるわけではない。その場合は -b オプションでバッチモードで起動する。
例えば、バッチモードで 2 秒毎に 3 回出力する場合は、top -b -d 2 -n 3
とする。
ps aux
ps aux
の %CPU の項目でプロセス単位の CPU 使用率が確認できる。こちらはすべてのプロセスが
確認できる。
$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 10348 680 ? Ss Feb21 0:15 init [5]
root 2 0.0 0.0 0 0 ? S< Feb21 5:34 [migration/0]
root 3 0.0 0.0 0 0 ? SN Feb21 0:10 [ksoftirqd/0]
root 4 0.0 0.0 0 0 ? S< Feb21 3:58 [migration/1]
root 5 0.0 0.0 0 0 ? SN Feb21 0:18 [ksoftirqd/1]
root 6 0.0 0.0 0 0 ? S< Feb21 33:22 [events/0]
root 7 0.0 0.0 0 0 ? S< Feb21 0:06 [events/1]
...
CPU を消費しているプロセスを特定したら、プロファイラ(OProfile, Valgrind(Callgrind)など)や
動的トレーサ(strace, ltrace など)で、どの関数で CPU を消費しているか特定していく。
簡易的には pstack
を定期的に採取して、どの関数を通っている割合が多そうか確認する。
物理メモリのサイズを確認するには次を実行。
$ grep MemTotal /proc/meminfo
MemTotal: 4044532 kB
スワップのサイズを確認するには次を実行。
$ grep SwapTotal /proc/meminfo
SwapTotal: 8159224 kB
メモリ使用量を確認して、物理メモリ以上使用されてスワップが多発していないか確認する。
vmstat
, free
, cat /proc/meminfo
同じタイミングで取得した vmstat
, free
, cat /proc/meminfo
の出力結果は以下
vmstat
の出力。
$ vmstat 2 3
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 629504 306172 181788 1825200 0 1 105 236 14 12 4 5 85 6 0
10 1 629504 306048 181788 1825200 0 0 35 732 300 1425 7 5 77 11 0
4 0 629504 306040 181788 1825204 0 0 27 2 236 1552 9 11 75 5 0
項目 | 説明 | |
---|---|---|
swapd | 仮想メモリの量(KB) | |
free | 空きメモリの量(KB) | |
buff | バッファに用いられているメモリの量(KB) | |
cache | キャッシュに用いられているメモリの量(KB) |
free
の出力
$ free
total used free shared buffers cached
Mem: 4044532 3738376 306156 0 181788 1825204
-/+ buffers/cache: 1731384 2313148
Swap: 8159224 629504 7529720
cat /proc/meminfo
の出力
$ cat /proc/meminfo
MemTotal: 4044532 kB
MemFree: 306148 kB
Buffers: 181788 kB
Cached: 1825204 kB
SwapCached: 358460 kB
Active: 2598312 kB
Inactive: 867084 kB
HighTotal: 0 kB
HighFree: 0 kB
LowTotal: 4044532 kB
LowFree: 306148 kB
SwapTotal: 8159224 kB
SwapFree: 7529720 kB
Dirty: 588 kB
Writeback: 0 kB
AnonPages: 1099912 kB
Mapped: 687964 kB
Slab: 153028 kB
PageTables: 63084 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
CommitLimit: 10181488 kB
Committed_AS: 6503324 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 286400 kB
VmallocChunk: 34359451067 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
Hugepagesize: 2048 kB
項目 | 説明 | |
---|---|---|
MemTotal | システム全体で利用できる物理メモリの総容量。システム起動時に計算される。その後、この値が変化することはない。 | |
MemFree | システム全体で利用できる物理メモリの空き容量 | |
Buffers | ファイルなどのメタデータとして使用している物理メモリの総容量 | |
Cached | ファイルデータのキャッシュなどに使用している物理メモリの総容量。共有メモリは Cached に加算される。SwapCachedは含まない。 | |
SwapCached | 物理メモリ上にキャッシュされたスワップページの総容量 | |
Active | 最近アクセスした(とカーネルが思っている)物理メモリの容量 | |
Inactive | 最近アクセスしていない(とカーネルが思っている)、解放してよい物理メモリの容量 | |
Slab | スラブアロケータで使用されている物理メモリの総容量 | |
VmallocUsed | vmalloc()で確保された物理メモリ領域とMMCONFIGで確保しているメモリ領域の総容量 | |
AnonPages | 無名ページ(Anonymous Page)の領域。無名ページとは、ユーザープロセスがmalloc()などで確保したり、プログラム本体用に利用するメモリ領域。 |
出力を確認すると以下が分かる。
vmstat
の free」 = 「free
の Mem: の free」 = 「cat /proc/meminfo
の MemFree」vmstat
の buff」 = 「free
の Mem: の buffers」 = 「cat /proc/meminfo
の Buffers」vmstat
の cache」 = 「free
の Mem: の cached」 = 「cat /proc/meminfo
の Cached」Linux では、空いているメモリはファイル I/O を効率化させるためにページキャッシュ として利用する。 buffers と cached はページキャッシュ(の一部)であり、実際はストレージと同期がとれ ていれば再利用可能なメモリである。
したがって利用可能な物理メモリ量は実際は、free + buffers + cached (free
の free+)となる。
|================= total =================|
|= free =|============= used =============|
+--------+----------+-----------+---------+
| | | | |
| | | | |
| | | | |
+--------+----------+-----------+---------+
|= cached =|= buffers =|
|============ free+ ============|= used- =|
厳密には、使用可能な物理メモリは「ストレージと同期されていない」ページキャッシュを除かないといけない。
ストレージと同期されており、すぐに再利用可能なメモリは「cat /proc/meminfo
の Inactive 」
(もしくは vmstat -a
)にて確認ができる。したがって、実際に再利用可能なメモリは厳密には「cat /proc/meminfo
の MemFree + Inactive」
となる。(概算としては、free + buffers + cached でよいと思う。)
ちなみに /proc/meminfo に関して原則として、以下のような計算式が成り立つ。
サーバ全体のスワップ状況は、vmstat
の swap 欄の si(ディスクからページインされるメモリの量 KB/秒),
so(ディスクにページアウトしているメモリ量 KB/秒)から確認する。
si, so が定常的に 0 より大きい場合は、スワップが発生しているのでメモリ不足に陥っている。
$ vmstat 2 # メモリを使用するプログラムを実行中
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------
r b swpd free buff cache si so bi bo in cs us sy id wa st
20 1 731484 36160 180152 1722248 0 1 105 238 7 9 4 5 84 6 0
16 1 731484 31292 180152 1722248 0 0 69 80 151 788 22 78 0 0 0
13 3 731484 26836 180152 1722248 0 0 19 168 149 807 23 69 8 0 0
2 0 733040 26528 179984 1720100 0 0 34 55 134 850 22 53 13 13 0
...<中略>
19 2 797140 24992 4276 787872 0 2804 56 2859 254 1024 38 62 0 0 0
19 1 814564 26708 2404 761484 0 5008 131 5191 246 912 37 63 0 0 0
6 1 816372 25500 2236 755376 0 626 113 882 156 599 35 65 0 0 0
28 4 824480 26208 1516 739372 0 3912 871 3934 265 1038 28 61 0 10 0
16 2 840912 24156 1232 725428 0 3166 963 3216 196 834 30 66 0 5 0
19 4 861804 25324 1268 718716 0 6790 323 7015 222 839 34 66 0 0 0
7 2 890208 28728 1296 709336 0 8648 878 8650 282 1061 30 66 2 3 0
19 3 919964 34312 1376 707076 0 10578 399 10820 293 1106 29 66 0 5 0
24 6 987308 55048 1384 707584 0 24256 293 24326 425 1415 36 64 0 0 0
2 9 987292 38232 1444 707816 32 0 207 158 189 738 48 50 0 2 0
6 2 1009184 33604 1504 709208 0 10228 780 10291 342 1047 30 58 5 7 0
5 2 1014212 26284 1536 709544 0 2078 259 2086 151 798 37 55 0 8 0
21 1 1030672 27936 1556 710044 16 7522 323 7758 225 944 37 58 2 3 0
10 1 1041180 26028 1604 710244 18 5318 239 5320 207 1013 38 62 0 0 0
24 5 1049788 28312 1632 710332 0 3618 31 3681 117 494 44 56 0 0 0
項目 | 説明 | |
---|---|---|
si | ディスクからスワップインされているメモリの量 (KB/s) | |
so | ディスクにスワップしているメモリの量 (KB/s) |
また、スワップ時は kswapd0
というカーネルスレッドが動作するので、これが top
などで確認して
CPU 使用率の上位に出現しているとスワップが多発している状況と判断できる。
プロセス単位のメモリ使用量を確認して、物理メモリを多く消費しているプロセスが無いか確認する。
top
VIRT, RES, SHR, %MEM から確認する。RES が物理メモリ使用量。
$ top
top - 11:41:10 up 46 days, 21:59, 3 users, load average: 9.69, 8.95, 7.96
Tasks: 226 total, 9 running, 217 sleeping, 0 stopped, 0 zombie
Cpu(s): 3.8%us, 4.4%sy, 0.1%ni, 84.5%id, 6.1%wa, 0.3%hi, 0.8%si, 0.0%st
Mem: 4044532k total, 2077900k used, 1966632k free, 7208k buffers
Swap: 8159224k total, 938488k used, 7220736k free, 745852k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
477 grid 15 0 718m 21m 16m S 1.0 0.6 2:35.70 oracle
667 grid 15 0 1247m 44m 16m S 1.0 1.1 15:30.20 oraagent.bin
1310 oracle 15 0 1781m 17m 15m S 1.0 0.4 1:34.40 oracle
1314 oracle 15 0 1800m 84m 64m S 1.0 2.1 21:53.70 oracle
32241 root 15 0 319m 51m 21m S 1.0 1.3 7:56.18 ohasd.bin
32360 grid 15 0 310m 35m 15m S 1.0 0.9 7:53.41 oraagent.bin
32582 root 16 0 240m 21m 10m S 1.0 0.6 8:48.50 octssd.bin
1 root 15 0 10348 672 568 S 0.0 0.0 0:15.40 init
項目 | 説明 | |
---|---|---|
VIRT | 使用している仮想メモリの総量 | |
RES | 使用しているスワップされていない物理メモリの総量 | |
SHR | 利用している共有メモリの総量。他のプロセスと共有される可能性がある。 | |
%MEM | 現在使用している利用可能な物理メモリの占有率 |
ps aux
%MEM, VSZ, RSS から確認する。RSS が物理メモリ使用量。
$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 10348 672 ? Ss Feb21 0:15 init [5]
root 2 0.0 0.0 0 0 ? S< Feb21 5:40 [migration/0]
...<中略>
grid 477 0.0 0.5 735924 22424 ? Ss Apr04 2:35 asm_gmon_+ASM1
...<中略>
grid 667 0.2 1.1 1277500 45740 ? Ssl Apr04 15:30 /u01/app/11.2.0.3/grid/bin/oraagent.bin
...<中略>
oracle 1310 0.0 0.4 1823952 17752 ? Ss Apr04 1:34 ora_ping_rac1
oracle 1312 0.0 0.4 1823952 17188 ? Ss Apr04 0:04 ora_acms_rac1
oracle 1314 0.3 2.1 1843460 86036 ? Ss Apr04 21:53 ora_dia0_rac1
...
項目 | 説明 | |
---|---|---|
%MEM | 現在使用している利用可能な物理メモリの占有率 | |
VSZ | 使用している仮想メモリの総量(KB) | |
RSS | 使用しているスワップされていない物理メモリの総量(KB) |
デバイス毎の I/O 状況を確認する。ビジー率が 100% に近かったり、IOPS, スループット(MB/s) が カタログ・スペックと比較して限界性能に近かったり、定常的に I/O キューが溜まっていると ボトルネックとなっている。
iostat -x
iostat -x
によりデバイス毎の I/O 状況が確認できる。
最初の1回目はシステムがブートしてからその時点までの統計情報であるので注意。
$ iostat -x 2 3
Linux 2.6.18-194.el5 (sv1.local) 04/09/2013
avg-cpu: %user %nice %system %iowait %steal %idle
1.63 0.01 1.58 1.91 0.00 94.88
Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s avgrq-sz avgqu-sz await svctm %util
sda 0.22 17.31 0.43 8.47 19.48 206.31 25.34 0.32 36.46 3.53 3.14
sda1 0.00 0.00 0.00 0.00 0.05 0.00 33.95 0.00 4.87 3.33 0.00
sda2 0.21 17.31 0.42 8.47 19.41 206.31 25.38 0.32 36.53 3.53 3.14
sdb 0.01 1.34 0.10 0.20 4.00 12.34 54.49 0.01 24.80 4.21 0.13
sdb1 0.01 1.34 0.10 0.20 4.00 12.34 54.60 0.01 24.85 4.22 0.13
sdc 0.07 13.21 0.52 4.53 49.09 141.94 37.83 0.16 31.58 8.10 4.09
sdc1 0.07 13.21 0.52 4.53 49.09 141.94 37.83 0.16 31.58 8.10 4.09
sdd 0.05 3.10 0.13 0.09 29.64 25.52 244.66 0.01 35.69 5.43 0.12
sdd1 0.05 3.10 0.13 0.09 29.63 25.52 245.32 0.01 35.79 5.45 0.12
dm-0 0.00 0.00 1.01 43.09 66.84 344.69 9.33 1.30 29.41 1.42 6.28
dm-1 0.00 0.00 0.21 0.45 1.65 3.57 8.00 0.03 40.23 0.35 0.02
dm-2 0.00 0.00 0.28 4.73 33.61 37.87 14.26 0.69 137.64 0.42 0.21
avg-cpu: %user %nice %system %iowait %steal %idle
1.43 0.00 0.00 2.86 0.00 95.71
Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s avgrq-sz avgqu-sz await svctm %util
sda 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
sda1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
sda2 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
sdb 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
sdb1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
sdc 0.00 7.14 0.00 4.29 0.00 91.43 21.33 0.49 114.33 107.00 45.86
sdc1 0.00 7.14 0.00 4.29 0.00 91.43 21.33 0.49 114.33 107.00 45.86
sdd 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
sdd1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
dm-0 0.00 0.00 0.00 11.43 0.00 91.43 8.00 1.83 159.88 40.12 45.86
dm-1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
dm-2 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
avg-cpu: %user %nice %system %iowait %steal %idle
1.19 0.00 0.00 1.19 0.00 97.62
Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s avgrq-sz avgqu-sz await svctm %util
sda 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
sda1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
sda2 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
sdb 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
sdb1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
sdc 0.00 5.95 0.00 2.38 0.00 47.62 20.00 0.03 6.50 8.00 1.90
sdc1 0.00 5.95 0.00 2.38 0.00 47.62 20.00 0.03 6.50 8.00 1.90
sdd 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
sdd1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
dm-0 0.00 0.00 0.00 9.52 0.00 76.19 8.00 0.07 4.62 2.00 1.90
dm-1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
dm-2 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
項目 | 説明 | |
---|---|---|
r/s | 読み込みリクエスト数(回/秒) | |
w/s | 書き込みリクエスト数(回/秒) | |
rsec/s | 読み込みセクタ数(個/秒)。1 セクタ = 512bytes なので、512 を掛ければ読み込み byte 量が分かる。 | |
wsec/s | 書き込みセクタ数(個/秒)。1 セクタ = 512bytes なので、512 を掛ければ書き込み byte 量が分かる。 | |
avgqu-sz | IOリクエストのキュー(待ち行列)の平均サイズ | |
await | IOリクエストの平均待ち時間(ミリ秒)。キューにいる時間+処理時間。 | |
svctm | IOリクエストの平均処理時間(ミリ秒) | |
%util | IOリクエスト実行中の CPU 時間の割合。この値が 100% に近いとビジーであり、ボトルネックとなる。 |
IOPS は r/s + w/s
で算出できる。
スループットについては、rsec/s
, wsec/s
に 512 を掛ければ byte 単位のスループット(bytes/s)が算出できる。
iostat -x -k
というように -k オプションを付与すると書込み量が kB/s で出力されるので見やすくなる。
ちなみに dm-*
で表されているデバイスは、デバイス・マッパーと呼ばれるもので Logical Volume に対応している。
lvdisplay
で出力される Block device
の項目の右の数字や、ls -l /dev/mapper
で LV との対応が分かる。
$ sudo /usr/sbin/lvdisplay
--- Logical volume ---
LV Name /dev/VolGroup01/LogVol00
VG Name VolGroup01
LV UUID hDNPM9-jBMV-7xto-mChi-ubR9-iN3i-styC8L
LV Write Access read/write
LV Status available
# open 1
LV Size 39.99 GB
Current LE 10237
Segments 2
Allocation inherit
Read ahead sectors auto
- currently set to 256
Block device 253:2
--- Logical volume ---
LV Name /dev/VolGroup00/LogVol00
VG Name VolGroup00
LV UUID 2V6IUo-8Eui-DHq1-Nnzl-g0Pf-Z8HN-FIKz2E
LV Write Access read/write
LV Status available
# open 1
LV Size 35.94 GB
Current LE 1150
Segments 2
Allocation inherit
Read ahead sectors auto
- currently set to 256
Block device 253:0
--- Logical volume ---
LV Name /dev/VolGroup00/LogVol01
VG Name VolGroup00
LV UUID sotwax-PxcG-hwzh-dCG7-tkO1-Vy2m-W5qk5M
LV Write Access read/write
LV Status available
# open 1
LV Size 3.91 GB
Current LE 125
Segments 1
Allocation inherit
Read ahead sectors auto
- currently set to 256
Block device 253:1
$ ls -l /dev/mapper
total 0
crw------- 1 root root 10, 63 Apr 9 16:50 control
brw-rw---- 1 root disk 253, 0 Apr 9 16:50 VolGroup00-LogVol00
brw-rw---- 1 root disk 253, 1 Apr 9 16:50 VolGroup00-LogVol01
brw-rw---- 1 root disk 253, 2 Apr 9 16:55 VolGroup01-LogVol00
ネットワークインターフェイス毎の ネットワーク使用状況を確認する。 ネットワーク帯域 bps (bit per second), 処理パケット数 pps (packet per second) といったスループットが カタログ・スペックと比較して限界性能に近いとボトルネックとなっている。 bps は (byte でなく) bit 単位であることに注意。
また、処理するパケット数が多い(chatty な処理)と、CPU のソフトウェア割り込みが多くなるので、CPU 使用率で
ソフトウェア割り込みが多くなっていないかも確認する。(mpstat
の %soft
の項目から確認できる。)
netstat -e -a -i -n
netstat -e -a -i -n
によりネットワークインターフェイス毎の ネットワーク使用状況が確認できる。
インターフェイス起動後からの累積値で表されるため、定期的に採取して差分を採る必要がある。
$ netstat -e -a -i -n
Kernel Interface table
eth0 Link encap:Ethernet HWaddr 00:0C:29:1C:4C:61
inet addr:192.168.238.138 Bcast:192.168.238.255 Mask:255.255.255.0
inet6 addr: fe80::20c:29ff:fe1c:4c61/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:8836237 errors:0 dropped:0 overruns:0 frame:0
TX packets:9148333 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:1491856405 (1.3 GiB) TX bytes:6627509379 (6.1 GiB)
eth0:1 Link encap:Ethernet HWaddr 00:0C:29:1C:4C:61
inet addr:192.168.238.141 Bcast:192.168.238.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
eth0:3 Link encap:Ethernet HWaddr 00:0C:29:1C:4C:61
inet addr:192.168.238.140 Bcast:192.168.238.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
eth0:5 Link encap:Ethernet HWaddr 00:0C:29:1C:4C:61
inet addr:192.168.238.143 Bcast:192.168.238.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
eth1 Link encap:Ethernet HWaddr 00:0C:29:1C:4C:6B
inet addr:192.168.13.14 Bcast:192.168.13.255 Mask:255.255.255.0
inet6 addr: fe80::20c:29ff:fe1c:4c6b/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:160311137 errors:0 dropped:0 overruns:0 frame:0
TX packets:139265311 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:118804988857 (110.6 GiB) TX bytes:84439638737 (78.6 GiB)
eth1:1 Link encap:Ethernet HWaddr 00:0C:29:1C:4C:6B
inet addr:169.254.94.158 Bcast:169.254.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:23503874 errors:0 dropped:0 overruns:0 frame:0
TX packets:23503874 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:20946402718 (19.5 GiB) TX bytes:20946402718 (19.5 GiB)
sit0 Link encap:IPv6-in-IPv4
NOARP MTU:1480 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 b) TX bytes:0 (0.0 b)
項目 | 説明 | |
---|---|---|
MTU | MTU 値 | |
RX packets | 受信パケット数 | |
TX packets | 送信パケット数 | |
RX bytes | 受信バイト数 | |
TX bytes | 送信バイト数 |
まとめると、以下のような点を確認してボトルネックとなっていないか特定する。
4.メモリ使用率(第5章 パフォーマンス管理~上級:基本管理コースII) のフローが参考になる。
+---------+
| vmstat |
+----+----+
|
V
+---------+ Yes +------------------+
| id < 10 +------->| CPU 使用率の評価 |
+----+----+ +------------------+
|
| No
V
+---------+ No +------------------+
| so > 0 +------->| Disk 使用率の評価|
+----+----+ +------------------+
|
| Yes
V
+----------+
|メモリ不足|
+----------+
+---------+
| vmstat |
+----+----+
|
V
+---------+ Yes
| sy > 30 +-------------+
+----+----+ |
| V
| No +----------+ No +------------------+
| | in > 200 +------->| Disk 使用率の評価|
| +----+-----+ +------------------+
| |
| | Yes
| V
| +------------------+
| |ハードウェアの問題|
V +------------------+
+----------+ Yes
| r > 0 +-------------+
+----+-----+ |
| |
| No |
V V
+------------+ +------------+
|CPU のアップ| | CPU の追加 |
|グレード | +------------+
+------------+
+---------+
|iostat -x|
+----+----+
|
V
+---------+ Yes +------------------+
|%util >80+------->|デバイスの負荷分散|
+----+----+ +------------------+
|
| No
V
+---------+ Yes +--------------------------+
|w/s > r/s+------->|ディスク・キャッシュの使用|
+----+----+ +--------------------------+
|
| No
V
+------------------+
|ネットワークの調査|
+------------------+
メモリリークの調査方法をまとめる。
メモリリークが起きるサンプルとして以下を利用する。
leak_func()
が実行される度に 2048 bytes メモリリークする。
合計で 101 回 leak_func()
が実行されるので 206848bytes(= 2048 * 101 bytes) リークする。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
|
実行結果は以下。
$ gcc -o memory_leak_sample memory_leak_sample.c
$ ./memory_leak_sample
freed memory: 0x0000000009a19010, leaked memory: 0x0000000009a19820
Press enter key:
freed memory: 0x0000000009a19010, leaked memory: 0x0000000009a1a030
freed memory: 0x0000000009a19010, leaked memory: 0x0000000009a1a840
freed memory: 0x0000000009a19010, leaked memory: 0x0000000009a1b050
<中略>
freed memory: 0x0000000009a19010, leaked memory: 0x0000000009a4b650
freed memory: 0x0000000009a19010, leaked memory: 0x0000000009a4be60
Press enter key:
Valgrind は、メモリリークの検出等を行うツール。
プロファイリング等メモリリーク検出以外の機能もあり、メモリリー検出で使用する場合は --tool=memcheck
を指定する。
Valgrind を使用して上記サンプルを動作させた例が以下。なお、Valgrind を通してプログラムを実行するとすごく遅くなるので注意。
$ valgrind --tool=memcheck --leak-check=yes --leak-resolution=high --num-callers=40 --undef-value-errors=no --run-libc-freeres=no -v ./memory_leak_sample
==16207== Memcheck, a memory error detector
==16207== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==16207== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==16207== Command: ./memory_leak_sample
==16207==
--16207-- Valgrind options:
--16207-- --tool=memcheck
--16207-- --leak-check=yes
--16207-- --leak-resolution=high
--16207-- --num-callers=40
--16207-- --undef-value-errors=no
--16207-- --run-libc-freeres=no
--16207-- -v
--16207-- Contents of /proc/version:
--16207-- Linux version 2.6.18-194.el5 (mockbuild@builder10.centos.org) (gcc version 4.1.2 20080704 (Red Hat 4.1.2-48)) #1 SMP Fri Apr 2
14:58:14 EDT 2010
--16207-- Arch and hwcaps: AMD64, amd64-sse3-cx16
--16207-- Page sizes: currently 4096, max supported 4096
--16207-- Valgrind library directory: /usr/lib64/valgrind
--16207-- Reading syms from /home/hashi/tmp/memory_leak_sample (0x400000)
--16207-- Reading syms from /usr/lib64/valgrind/memcheck-amd64-linux (0x38000000)
--16207-- object doesn't have a dynamic symbol table
--16207-- Reading syms from /lib64/ld-2.5.so (0x3e1c600000)
--16207-- Reading suppressions file: /usr/lib64/valgrind/default.supp
--16207-- REDIR: 0x3e1c614620 (strlen) redirected to 0x3803e767 (vgPlain_amd64_linux_REDIR_FOR_strlen)
--16207-- Reading syms from /usr/lib64/valgrind/vgpreload_core-amd64-linux.so (0x4802000)
--16207-- Reading syms from /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so (0x4a03000)
==16207== WARNING: new redirection conflicts with existing -- ignoring it
--16207-- new: 0x3e1c614620 (strlen ) R-> 0x04a06dc0 strlen
--16207-- REDIR: 0x3e1c614440 (index) redirected to 0x4a06c30 (index)
--16207-- REDIR: 0x3e1c6145f0 (strcmp) redirected to 0x4a06e90 (strcmp)
--16207-- Reading syms from /lib64/libc-2.5.so (0x3e1ca00000)
--16207-- REDIR: 0x3e1ca79ba0 (rindex) redirected to 0x4a06ae0 (rindex)
--16207-- REDIR: 0x3e1ca74c70 (malloc) redirected to 0x4a05d9a (malloc)
--16207-- REDIR: 0x3e1ca797b0 (strlen) redirected to 0x4a06d80 (strlen)
freed memory: 0x0000000004c24040, leaked memory: 0x0000000004c24880
--16207-- REDIR: 0x3e1ca72720 (free) redirected to 0x4a059aa (free)
Press enter key:
freed memory: 0x0000000004c250c0, leaked memory: 0x0000000004c25900
freed memory: 0x0000000004c26140, leaked memory: 0x0000000004c26980
<中略>
freed memory: 0x0000000004c89140, leaked memory: 0x0000000004c89980
freed memory: 0x0000000004c8a1c0, leaked memory: 0x0000000004c8aa00
freed memory: 0x0000000004c8b240, leaked memory: 0x0000000004c8ba80
Press enter key:
==16207==
==16207== HEAP SUMMARY:
==16207== in use at exit: 206,848 bytes in 101 blocks
==16207== total heap usage: 202 allocs, 101 frees, 413,696 bytes allocated
==16207==
==16207== Searching for pointers to 101 not-freed blocks
==16207== Checked 73,400 bytes
==16207==
==16207== 2,048 bytes in 1 blocks are definitely lost in loss record 1 of 2
==16207== at 0x4A05E1C: malloc (vg_replace_malloc.c:195)
==16207== by 0x40069C: my_alloc (in /home/hashi/tmp/memory_leak_sample)
==16207== by 0x400719: leak_func (in /home/hashi/tmp/memory_leak_sample)
==16207== by 0x4007AE: main (in /home/hashi/tmp/memory_leak_sample)
==16207==
==16207== 204,800 bytes in 100 blocks are definitely lost in loss record 2 of 2
==16207== at 0x4A05E1C: malloc (vg_replace_malloc.c:195)
==16207== by 0x40069C: my_alloc (in /home/hashi/tmp/memory_leak_sample)
==16207== by 0x400719: leak_func (in /home/hashi/tmp/memory_leak_sample)
==16207== by 0x4007D5: main (in /home/hashi/tmp/memory_leak_sample)
==16207==
==16207== LEAK SUMMARY:
==16207== definitely lost: 206,848 bytes in 101 blocks
==16207== indirectly lost: 0 bytes in 0 blocks
==16207== possibly lost: 0 bytes in 0 blocks
==16207== still reachable: 0 bytes in 0 blocks
==16207== suppressed: 0 bytes in 0 blocks
==16207==
==16207== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
==16207== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
いろいろ出力があるが、以下の出力からどの Call Stack で確保されたメモリがどれだけリークしているか分かる。
==16207== 2,048 bytes in 1 blocks are definitely lost in loss record 1 of 2
==16207== at 0x4A05E1C: malloc (vg_replace_malloc.c:195)
==16207== by 0x40069C: my_alloc (in /home/hashi/tmp/memory_leak_sample)
==16207== by 0x400719: leak_func (in /home/hashi/tmp/memory_leak_sample)
==16207== by 0x4007AE: main (in /home/hashi/tmp/memory_leak_sample)
==16207==
==16207== 204,800 bytes in 100 blocks are definitely lost in loss record 2 of 2
==16207== at 0x4A05E1C: malloc (vg_replace_malloc.c:195)
==16207== by 0x40069C: my_alloc (in /home/hashi/tmp/memory_leak_sample)
==16207== by 0x400719: leak_func (in /home/hashi/tmp/memory_leak_sample)
==16207== by 0x4007D5: main (in /home/hashi/tmp/memory_leak_sample)
上述のやり方だと、シェルから valgrind と共に直接実行できるプログラムでないと調査できない。 デーモンなど直接実行できないプログラムの場合は、ラップするシェルスクリプトを用意するとよい。
例えば、最終的に some_daemon.bin
というバイナリが実行されるデーモンがあるとして、以下の
ようにラップするシェルスクリプトを同一名で用意して、元のデーモンと同じように起動・停止すればよい。
$ cp -p /path/to/some_daemon.bin /path/to/some_daemon.bin.backup
$ mv /path/to/some_daemon.bin /path/to/some_daemon.bin.orig
$ vi some_daemon.bin # 以下の内容で作成
--------
#!/bin/sh
ORG_BIN=/path/to/some_daemon.bin.orig
LOG_LOC_AND_PREFIX=/tmp/valgrind_instance.%p.log
VALG_PATH=/usr/bin/valgrind
VALGRIND_OPTS="--log-file=$LOG_LOC_AND_PREFIX --leak-check=yes --leak-resolution=high --num-callers=40 --undef-value-errors=no --run-libc-freeres=no --error-limit=no -v"
export VALGRIND_OPTS
exec $VALG_PATH --tool=memcheck $ORG_BIN "$*"
--------
$ chown root:root some_daemon.bin # 元のバイナリと同じオーナーにする
$ chmod 755 some_daemon.bin # 元のバイナリと同じパーミッションにする
これで上記LOG_LOC_AND_PREFIX
に指定したファイルにログが出力される。
プロセスのメモリマップを表示する pmap
を採取してリークしている領域を特定し、その内容を gdb
から確認する。
まず、pmap
を採取して増加している領域を特定する。合わせメモリダンプを確認するために gcore
により core を採取しておく。
プログラムを実行
$ ./memory_leak_sample
freed memory: 0x0000000007b8e010, leaked memory: 0x0000000007b8e820
Press enter key:
この時の pmap の結果は以下
$ ps -ef | grep memory_leak_sample
hashi 16639 29479 0 10:47 pts/10 00:00:00 ./memory_leak_sample
$ pmap -x 16639
16639: ./memory_leak_sample
Address Kbytes RSS Dirty Mode Mapping
0000000000400000 4 4 0 r-x-- memory_leak_sample
0000000000600000 4 4 4 rw--- memory_leak_sample
0000000007b8e000 132 8 8 rw--- [ anon ]
0000003e1c600000 112 96 0 r-x-- ld-2.5.so
0000003e1c81b000 4 4 4 r---- ld-2.5.so
0000003e1c81c000 4 4 4 rw--- ld-2.5.so
0000003e1ca00000 1336 260 0 r-x-- libc-2.5.so
0000003e1cb4e000 2044 0 0 ----- libc-2.5.so
0000003e1cd4d000 16 12 8 r---- libc-2.5.so
0000003e1cd51000 4 4 4 rw--- libc-2.5.so
0000003e1cd52000 20 16 16 rw--- [ anon ]
00002b2fac886000 12 8 8 rw--- [ anon ]
00002b2fac89f000 8 8 8 rw--- [ anon ]
00007fff1b3a6000 84 12 12 rw--- [ stack ]
ffffffffff600000 8192 0 0 ----- [ anon ]
---------------- ------ ------ ------
total kB 11976 440 76
合わせて core を採取しておく(上書きされないようにリネームしておく)
$ gcore 16639
0x0000003e1cac5ff0 in __read_nocancel () from /lib64/libc.so.6
Saved corefile core.16639
$ mv core.16639 core.16639.before
プログラムを進める
$ ./memory_leak_sample
freed memory: 0x0000000007b8e010, leaked memory: 0x0000000007b8e820
Press enter key: <=== エンターキーを押下
freed memory: 0x0000000007b8e010, leaked memory: 0x0000000007b8f030
freed memory: 0x0000000007b8e010, leaked memory: 0x0000000007b8f840
<中略>
freed memory: 0x0000000007b8e010, leaked memory: 0x0000000007bae410
freed memory: 0x0000000007b8e010, leaked memory: 0x0000000007baec20
freed memory: 0x0000000007b8e010, leaked memory: 0x0000000007baf430
freed memory: 0x0000000007b8e010, leaked memory: 0x0000000007bafc40
freed memory: 0x0000000007b8e010, leaked memory: 0x0000000007bb0450
<中略>
freed memory: 0x0000000007b8e010, leaked memory: 0x0000000007bc0650
freed memory: 0x0000000007b8e010, leaked memory: 0x0000000007bc0e60
Press enter key:
この時の pmap と core を採取しておく
$ pmap -x 16639
16639: ./memory_leak_sample
Address Kbytes RSS Dirty Mode Mapping
0000000000400000 4 4 0 r-x-- memory_leak_sample
0000000000600000 4 4 4 rw--- memory_leak_sample
0000000007b8e000 264 208 208 rw--- [ anon ]
0000003e1c600000 112 96 0 r-x-- ld-2.5.so
0000003e1c81b000 4 4 4 r---- ld-2.5.so
0000003e1c81c000 4 4 4 rw--- ld-2.5.so
0000003e1ca00000 1336 260 0 r-x-- libc-2.5.so
0000003e1cb4e000 2044 0 0 ----- libc-2.5.so
0000003e1cd4d000 16 12 8 r---- libc-2.5.so
0000003e1cd51000 4 4 4 rw--- libc-2.5.so
0000003e1cd52000 20 16 16 rw--- [ anon ]
00002b2fac886000 12 12 12 rw--- [ anon ]
00002b2fac89f000 8 8 8 rw--- [ anon ]
00007fff1b3a6000 84 12 12 rw--- [ stack ]
ffffffffff600000 8192 0 0 ----- [ anon ]
---------------- ------ ------ ------
total kB 12108 644 280
$ gcore 16639
0x0000003e1cac5ff0 in __read_nocancel () from /lib64/libc.so.6
Saved corefile core.16639
$ mv core.16639 core.16639.after
1回目と2回目の pmap の結果を比較すると、以下の個所で仮想メモリ量が増加している(リークしている)ことが分かる。
5c5
< 0000000007b8e000 132 8 8 rw--- [ anon ]
---
> 0000000007b8e000 264 208 208 rw--- [ anon ]
仮想メモリ量が 132Kbytes –> 264Kbytes (+132Kbytes)に増加している。 増加したアドレスのメモリダンプを確認して、どのように利用されているか確認してみる。
具体的にはメモリ増加後の core で 0x0000000007b8e000 + 132Kbytes のアドレス(0x7baf000)から
増加した 132Kbytes 分のメモリダンプを確認する。
ダンプを採るときに x/16896xg 0x7baf000
としているのは、アドレス 0x7baf000
から、
8バイト(ジャイアント・ワード)単位で(g
)、16896個分を、16進数で(x
)出力するということ。
つまり、16896 * 8 = 135168 = 132Kbytes
分が出力される。
$ gdb
(gdb) set height 0
(gdb) file ./memory_leak_sample
(gdb) core-file core.16639.after
(gdb) set logging file core.16639.after.gdb.log
(gdb) set logging on
(gdb) x/16896xg 0x7baf000
0x7baf000: 0x0000000000000000 0x0000000000000000
0x7baf010: 0x0000000000000000 0x0000000000000000
<中略>
0x7baf410: 0x0000000000000000 0x0000000000000000
0x7baf420: 0x0000000000000000 0x0000000000000811
0x7baf430: 0x6d2064656b61656c 0x00000079726f6d65
0x7baf440: 0x0000000000000000 0x0000000000000000
<中略>
0x7bcffe0: 0x0000000000000000 0x0000000000000000
0x7bcfff0: 0x0000000000000000 0x0000000000000000
(gdb) quit
このメモリダンプが pmap 上増加した分。内容を確認すると以下の文字列が見える。
$ grep -v "0x0000000000000000" core.16639.after.gdb.log
0x7baf430: 0x6d2064656b61656c 0x00000079726f6d65
0x7bafc40: 0x6d2064656b61656c 0x00000079726f6d65
0x7bb0450: 0x6d2064656b61656c 0x00000079726f6d65
0x7bb0c60: 0x6d2064656b61656c 0x00000079726f6d65
<中略>
0x7bbf630: 0x6d2064656b61656c 0x00000079726f6d65
0x7bbfe40: 0x6d2064656b61656c 0x00000079726f6d65
0x7bc0650: 0x6d2064656b61656c 0x00000079726f6d65
0x7bc0e60: 0x6d2064656b61656c 0x00000079726f6d65
0x6d2064656b61656c 0x00000079726f6d65
は ASCII で直すと “leaked memory”
$ ruby -e 's="6d2064656b61656c"; s.unpack("a2" * (s.size / 2)) {|c| print c.hex.chr}; puts'
m dekael
$ ruby -e 's="00000079726f6d65"; s.unpack("a2" * (s.size / 2)) {|c| print c.hex.chr}; puts'
yrome
よってプログラム中で “leaked memory” を入れている領域がリークしていると分かる。 こんなに明らかに分かるケースは少なく、実際のプログラムではポインタが見えていたり して、さらに core を追わないとリーク原因箇所が追えないケースが多いと思うがメモリ リーク原因究明のとっかかりにはなる。
gdb による core のメモリダンプは dump binary memory
によって行うことができる。
(こちらのほうが x
でダンプするより速い。)
(gdb) help dump binary memory
Write contents of memory to a raw binary file.
Arguments are FILE START STOP. Writes the contents of memory
within the range [START .. STOP) to the specifed FILE in binary format.
例えば上述したもの同じように core で 0x7baf000 から 132Kbytes 分(0x7bd0000 まで)メモリダンプする場合は 以下のように行う。
$ gdb
(gdb) core-file core.16639.after
(gdb) dump binary memory core.16639.after.gdb.dump.log 0x7baf000 0x7bd0000
(gdb) quit
これでファイル core.16639.after.gdb.dump.log
にダンプされている。
中身はバイナリなので od
や hexdump
で内容を確認する。
$ od -v -t x8z -A x ./core.16639.after.gdb.dump.log
000000 0000000000000000 0000000000000000 >................<
000010 0000000000000000 0000000000000000 >................<
<中略>
000410 0000000000000000 0000000000000000 >................<
000420 0000000000000000 0000000000000811 >................<
000430 6d2064656b61656c 00000079726f6d65 >leaked memory...<
000440 0000000000000000 0000000000000000 >................<
<中略>
020fe0 0000000000000000 0000000000000000 >................<
020ff0 0000000000000000 0000000000000000 >................<
021000
同じ内容が連続する場合に省略する場合は -v
を付けなければよい。
$ od -t x8z -A x ./core.16639.after.gdb.dump.log
000000 0000000000000000 0000000000000000 >................<
*
000420 0000000000000000 0000000000000811 >................<
000430 6d2064656b61656c 00000079726f6d65 >leaked memory...<
000440 0000000000000000 0000000000000000 >................<
*
000c30 0000000000000000 0000000000000811 >................<
000c40 6d2064656b61656c 00000079726f6d65 >leaked memory...<
000c50 0000000000000000 0000000000000000 >................<
*
<中略>
011e50 0000000000000000 0000000000000811 >................<
011e60 6d2064656b61656c 00000079726f6d65 >leaked memory...<
011e70 0000000000000000 0000000000000000 >................<
*
012660 0000000000000000 000000000000e9a1 >................<
012670 0000000000000000 0000000000000000 >................<
*
021000
gdb の info target
とか info files
で pmap
と同じような情報を確認できる。
(pmap
を採っていなくても core から同じような情報が何とか見れる。)
$ gdb
(gdb) file ./memory_leak_sample
(gdb) core-file core.16639.after
(gdb) info target
Symbols from "/home/hashi/tmp/memory_leak_sample".
Local core dump file:
`/home/hashi/tmp/core.16639.after', file type elf64-x86-64.
0x0000000000400000 - 0x0000000000400000 is load1
0x0000000000600000 - 0x0000000000601000 is load2
0x0000000007b8e000 - 0x0000000007bd0000 is load3
0x0000003e1c600000 - 0x0000003e1c600000 is load4
0x0000003e1c81b000 - 0x0000003e1c81b000 is load5
0x0000003e1c81c000 - 0x0000003e1c81d000 is load6
0x0000003e1ca00000 - 0x0000003e1ca00000 is load7
0x0000003e1cd4d000 - 0x0000003e1cd4d000 is load8
0x0000003e1cd51000 - 0x0000003e1cd52000 is load9
0x0000003e1cd52000 - 0x0000003e1cd57000 is load10
0x00002b2fac886000 - 0x00002b2fac889000 is load11
0x00002b2fac89f000 - 0x00002b2fac8a1000 is load12
0x00007fff1b3a6000 - 0x00007fff1b3bb000 is load13
Local exec file:
`/home/hashi/tmp/memory_leak_sample', file type elf64-x86-64.
Entry point: 0x4005b0
0x0000000000400200 - 0x000000000040021c is .interp
0x000000000040021c - 0x000000000040023c is .note.ABI-tag
0x0000000000400240 - 0x0000000000400264 is .gnu.hash
0x0000000000400268 - 0x0000000000400370 is .dynsym
0x0000000000400370 - 0x00000000004003d8 is .dynstr
0x00000000004003d8 - 0x00000000004003ee is .gnu.version
0x00000000004003f0 - 0x0000000000400410 is .gnu.version_r
0x0000000000400410 - 0x0000000000400440 is .rela.dyn
0x0000000000400440 - 0x0000000000400500 is .rela.plt
0x0000000000400500 - 0x0000000000400518 is .init
0x0000000000400518 - 0x00000000004005a8 is .plt
0x00000000004005b0 - 0x00000000004008d8 is .text
0x00000000004008d8 - 0x00000000004008e6 is .fini
0x00000000004008e8 - 0x0000000000400957 is .rodata
0x0000000000400958 - 0x0000000000400994 is .eh_frame_hdr
0x0000000000400998 - 0x0000000000400a8c is .eh_frame
0x0000000000600a90 - 0x0000000000600aa0 is .ctors
0x0000000000600aa0 - 0x0000000000600ab0 is .dtors
0x0000000000600ab0 - 0x0000000000600ab8 is .jcr
0x0000000000600ab8 - 0x0000000000600c48 is .dynamic
0x0000000000600c48 - 0x0000000000600c50 is .got
0x0000000000600c50 - 0x0000000000600ca8 is .got.plt
0x0000000000600ca8 - 0x0000000000600cac is .data
0x0000000000600cb0 - 0x0000000000600cc8 is .bss
アドレスぐらいしか分からないが Local core dump file:
の項が pmap
の出力と対応している。
(アドレスを引けばサイズが分かる。)
Local core dump file:
`/home/hashi/tmp/core.16639.after', file type elf64-x86-64.
0x0000000000400000 - 0x0000000000400000 is load1 <=== TEXT 領域など
0x0000000000600000 - 0x0000000000601000 is load2 <=== BSS 領域など
0x0000000007b8e000 - 0x0000000007bd0000 is load3 <=== size: 264Kbytes (= 0x0000000007bd0000 - 0x0000000007b8e000)
0x0000003e1c600000 - 0x0000003e1c600000 is load4 <=== size: 0?
0x0000003e1c81b000 - 0x0000003e1c81b000 is load5 <=== size: 0?
0x0000003e1c81c000 - 0x0000003e1c81d000 is load6 <=== size: 4Kbytes
0x0000003e1ca00000 - 0x0000003e1ca00000 is load7 <=== size: 0?
0x0000003e1cd4d000 - 0x0000003e1cd4d000 is load8 <=== size: 0?
0x0000003e1cd51000 - 0x0000003e1cd52000 is load9 <=== size: 4Kbytes
0x0000003e1cd52000 - 0x0000003e1cd57000 is load10 <=== size: 20Kbytes
0x00002b2fac886000 - 0x00002b2fac889000 is load11 <=== size: 12Kbytes
0x00002b2fac89f000 - 0x00002b2fac8a1000 is load12 <=== size: 8Kbytes
0x00007fff1b3a6000 - 0x00007fff1b3bb000 is load13 <=== size: 84Kbytes
ちなみに maintenace info sections
でも同じような情報が採れる。
(gdb) maintenance info sections
Exec file:
`/home/hashi/tmp/memory_leak_sample', file type elf64-x86-64.
0x00400200->0x0040021c at 0x00000200: .interp ALLOC LOAD READONLY DATA HAS_CONTENTS
0x0040021c->0x0040023c at 0x0000021c: .note.ABI-tag ALLOC LOAD READONLY DATA HAS_CONTENTS
0x00400240->0x00400264 at 0x00000240: .gnu.hash ALLOC LOAD READONLY DATA HAS_CONTENTS
0x00400268->0x00400370 at 0x00000268: .dynsym ALLOC LOAD READONLY DATA HAS_CONTENTS
0x00400370->0x004003d8 at 0x00000370: .dynstr ALLOC LOAD READONLY DATA HAS_CONTENTS
0x004003d8->0x004003ee at 0x000003d8: .gnu.version ALLOC LOAD READONLY DATA HAS_CONTENTS
0x004003f0->0x00400410 at 0x000003f0: .gnu.version_r ALLOC LOAD READONLY DATA HAS_CONTENTS
0x00400410->0x00400440 at 0x00000410: .rela.dyn ALLOC LOAD READONLY DATA HAS_CONTENTS
0x00400440->0x00400500 at 0x00000440: .rela.plt ALLOC LOAD READONLY DATA HAS_CONTENTS
0x00400500->0x00400518 at 0x00000500: .init ALLOC LOAD READONLY CODE HAS_CONTENTS
0x00400518->0x004005a8 at 0x00000518: .plt ALLOC LOAD READONLY CODE HAS_CONTENTS
0x004005b0->0x004008d8 at 0x000005b0: .text ALLOC LOAD READONLY CODE HAS_CONTENTS
0x004008d8->0x004008e6 at 0x000008d8: .fini ALLOC LOAD READONLY CODE HAS_CONTENTS
0x004008e8->0x00400957 at 0x000008e8: .rodata ALLOC LOAD READONLY DATA HAS_CONTENTS
0x00400958->0x00400994 at 0x00000958: .eh_frame_hdr ALLOC LOAD READONLY DATA HAS_CONTENTS
0x00400998->0x00400a8c at 0x00000998: .eh_frame ALLOC LOAD READONLY DATA HAS_CONTENTS
0x00600a90->0x00600aa0 at 0x00000a90: .ctors ALLOC LOAD DATA HAS_CONTENTS
0x00600aa0->0x00600ab0 at 0x00000aa0: .dtors ALLOC LOAD DATA HAS_CONTENTS
0x00600ab0->0x00600ab8 at 0x00000ab0: .jcr ALLOC LOAD DATA HAS_CONTENTS
0x00600ab8->0x00600c48 at 0x00000ab8: .dynamic ALLOC LOAD DATA HAS_CONTENTS
0x00600c48->0x00600c50 at 0x00000c48: .got ALLOC LOAD DATA HAS_CONTENTS
0x00600c50->0x00600ca8 at 0x00000c50: .got.plt ALLOC LOAD DATA HAS_CONTENTS
0x00600ca8->0x00600cac at 0x00000ca8: .data ALLOC LOAD DATA HAS_CONTENTS
0x00600cb0->0x00600cc8 at 0x00000cac: .bss ALLOC
0x00000000->0x00000114 at 0x00000cac: .comment READONLY HAS_CONTENTS
Core file:
`/home/hashi/tmp/core.16639.after', file type elf64-x86-64.
0x00000000->0x00000528 at 0x00000350: note0 READONLY HAS_CONTENTS
0x00000000->0x000000d8 at 0x00000470: .reg/16639 HAS_CONTENTS
0x00000000->0x000000d8 at 0x00000470: .reg HAS_CONTENTS
0x00000000->0x00000200 at 0x00000564: .reg2/16639 HAS_CONTENTS
0x00000000->0x00000200 at 0x00000564: .reg2 HAS_CONTENTS
0x00000000->0x00000100 at 0x00000778: .auxv HAS_CONTENTS
0x00400000->0x00400000 at 0x00000878: load1 ALLOC READONLY CODE
0x00600000->0x00601000 at 0x00000878: load2 ALLOC LOAD HAS_CONTENTS
0x07b8e000->0x07bd0000 at 0x00001878: load3 ALLOC LOAD HAS_CONTENTS
0x3e1c600000->0x3e1c600000 at 0x00043878: load4 ALLOC READONLY CODE
0x3e1c81b000->0x3e1c81b000 at 0x00043878: load5 ALLOC READONLY
0x3e1c81c000->0x3e1c81d000 at 0x00043878: load6 ALLOC LOAD HAS_CONTENTS
0x3e1ca00000->0x3e1ca00000 at 0x00044878: load7 ALLOC READONLY CODE
0x3e1cd4d000->0x3e1cd4d000 at 0x00044878: load8 ALLOC READONLY
0x3e1cd51000->0x3e1cd52000 at 0x00044878: load9 ALLOC LOAD HAS_CONTENTS
0x3e1cd52000->0x3e1cd57000 at 0x00045878: load10 ALLOC LOAD HAS_CONTENTS
0x2b2fac886000->0x2b2fac889000 at 0x0004a878: load11 ALLOC LOAD HAS_CONTENTS
0x2b2fac89f000->0x2b2fac8a1000 at 0x0004d878: load12 ALLOC LOAD HAS_CONTENTS
0x7fff1b3a6000->0x7fff1b3bb000 at 0x0004f878: load13 ALLOC LOAD HAS_CONTENTS
なお、info proc mappings
というコマンドもあるが、これはプロセス起動中に gdb でアタッチしたときに
使用して /proc
から情報を採るものなので、core からの調査には使えないようだ。
(core に対して実行すると PID:1 の /sbin/init
の /proc/1/maps
情報が出てきてしまい役に立たない。)
リークしている領域の特徴に当たりがついていれば gdb の find
で見つけるという方法もある。
(gdb) help find
Search memory for a sequence of bytes.
Usage:
find [/size-char] [/max-count] start-address, end-address, expr1 [, expr2 ...]
find [/size-char] [/max-count] start-address, +length, expr1 [, expr2 ...]
size-char is one of b,h,w,g for 8,16,32,64 bit values respectively,
and if not specified the size is taken from the type of the expression
in the current language.
Note that this means for example that in the case of C-like languages
a search for an untyped 0x42 will search for "(int) 0x42"
which is typically four bytes.
The address of the last match is stored as the value of "$_".
Convenience variable "$numfound" is set to the number of matches.
例えば上述の例で 0x7baf000 から 132Kbytes 分の間に “leaked memory” という文字列を見つける。
$ gdb
(gdb) core-file core.16639.after
(gdb) find 0x7baf000, +(132 * 1024), "leaked memory"
0x7baf430
0x7bafc40
0x7bb0450
0x7bb0c60
0x7bb1470
0x7bb1c80
0x7bb2490
0x7bb2ca0
0x7bb34b0
0x7bb3cc0
0x7bb44d0
0x7bb4ce0
0x7bb54f0
0x7bb5d00
0x7bb6510
0x7bb6d20
0x7bb7530
0x7bb7d40
0x7bb8550
0x7bb8d60
0x7bb9570
0x7bb9d80
0x7bba590
0x7bbada0
0x7bbb5b0
0x7bbbdc0
0x7bbc5d0
0x7bbcde0
0x7bbd5f0
0x7bbde00
0x7bbe610
0x7bbee20
0x7bbf630
0x7bbfe40
0x7bc0650
0x7bc0e60
36 patterns found.
36 個所で見つかった。
なお、find
の引数で文字列を渡して探すときは文字列は NULL 終端を含むことに注意。
例えば、上記の例で部分文字列で探そうと “leaked” としても引っかからない。
(gdb) find 0x7baf000, +(132 * 1024), "leaked"
Pattern not found.
部分文字列で探す場合は文字列を16進数にするしかなさそう。
(リトルエンディアンで “leaked” は 0x64656b61656c
になる。)
(gdb) find /b 0x7baf000, +(132 * 1024), 0x64656b61656c
0x7baf430
0x7bafc40
<中略>
0x7bc0650
0x7bc0e60
36 patterns found.
]]>仮想メモリ空間のアドレス等のメモリマップを調べる。
なお、ちゃんと調べたわけではないので誤りがあるかもしれない。
Unix/Linux における仮想メモリ空間のメモリマップは一般には以下のようになっている。
+------------------------------+ 0x0000000000000000
: :
+------------------------------+
| |
| text | 機械命令
| |
+------------------------------+
| |
| data | 初期化された static 変数
| |
+------------------------------+
| |
| BSS | 初期化されていない static 変数
| |
+------------------------------+
| |
| heap | malloc() で動的に確保される領域(上位アドレスに伸びる)
| |
+------------------------------+
| |||| |
| VVVV |
: :
: :
| |
+------------------------------+
| |
| shared memory | 共有メモリ領域
| |
+------------------------------+
| |
: :
: :
| ^^^^ |
| |||| |
+------------------------------+
| |
| stack | 関数呼び出しやローカル変数等で使用されるスタック領域(下位アドレスに伸びる)
| |
+------------------------------+
| |
| arguments / environments | 引数と環境変数
| |
+------------------------------+
: :
: :
+------------------------------+ 0xffffffffffffffff = 2^64 (64bit の場合)
以下のプログラムでメモリマップを確認する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
|
実行結果は以下。
$ gcc -o hello hello.c
$ ./hello ARGworld
hello, ARGworld: 0x00007ffff7269cb5
hello, not initialized global world: 0x0000000000600ec0
hello, initialized global world: 0x0000000000600de0
hello, not initialized static world: 0x0000000000600e80
hello, initialized static world: 0x0000000000600e20
hello, malloc world: 0x0000000001f61010
hello, shared memory world: 0x00007f2eeea0e000
hello, big malloc world: 0x00007f2eee9b0010
hello, medium malloc world 1: 0x00007f2eee98f010
hello, medium malloc world 2: 0x0000000001f61060
hello, local world: 0x00007ffff7269680
メモリマップは pmap
や cat /proc/<PID>/maps
や cat /proc/<PID>/smaps
で確認できる。
$ pmap -x 19671
19671: ./hello ARGworld
Address Kbytes RSS Dirty Mode Mapping
0000000000400000 4 4 0 r-x-- hello
0000000000600000 4 4 4 rw--- hello
0000000001f61000 132 8 8 rw--- [ anon ]
00000037be000000 112 96 0 r-x-- ld-2.5.so
00000037be21c000 4 4 4 r---- ld-2.5.so
00000037be21d000 4 4 4 rw--- ld-2.5.so
00000037be400000 1340 248 0 r-x-- libc-2.5.so
00000037be54f000 2044 0 0 ----- libc-2.5.so
00000037be74e000 16 12 8 r---- libc-2.5.so
00000037be752000 4 4 4 rw--- libc-2.5.so
00000037be753000 20 16 16 rw--- [ anon ]
00007f2eee98d000 408 20 20 rw--- [ anon ]
00007f2eeea0e000 4 4 4 rw-s- [ shmid=0x578006 ]
00007f2eeea0f000 8 8 8 rw--- [ anon ]
00007ffff7255000 84 8 8 rw--- [ stack ]
00007ffff730e000 4 4 0 r-x-- [ anon ]
ffffffffff600000 4 0 0 r-x-- [ anon ]
---------------- ------ ------ ------
total kB 4196 444 88
$ cat /proc/19671/maps
00400000-00401000 r-xp 00000000 fd:00 5101878 /tmp/hello
00600000-00601000 rw-p 00000000 fd:00 5101878 /tmp/hello
01f61000-01f82000 rw-p 00000000 00:00 0 [heap]
37be000000-37be01c000 r-xp 00000000 fd:00 1871272 /lib64/ld-2.5.so
37be21c000-37be21d000 r--p 0001c000 fd:00 1871272 /lib64/ld-2.5.so
37be21d000-37be21e000 rw-p 0001d000 fd:00 1871272 /lib64/ld-2.5.so
37be400000-37be54f000 r-xp 00000000 fd:00 1871273 /lib64/libc-2.5.so
37be54f000-37be74e000 ---p 0014f000 fd:00 1871273 /lib64/libc-2.5.so
37be74e000-37be752000 r--p 0014e000 fd:00 1871273 /lib64/libc-2.5.so
37be752000-37be753000 rw-p 00152000 fd:00 1871273 /lib64/libc-2.5.so
37be753000-37be758000 rw-p 00000000 00:00 0
7f2eee98d000-7f2eee9f3000 rw-p 00000000 00:00 0
7f2eeea0e000-7f2eeea0f000 rw-s 00000000 00:04 5734406 /SYSV00000000 (deleted)
7f2eeea0f000-7f2eeea11000 rw-p 00000000 00:00 0
7ffff7255000-7ffff726a000 rw-p 00000000 00:00 0 [stack]
7ffff730e000-7ffff730f000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
size
コマンドで text や BSS のサイズを確認できる。
$ size hello
text data bss dec hex filename
2414 704 160 3278 cce hello
$ size --format=SysV -x hello
hello :
section size addr
.interp 0x1c 0x400200
.note.ABI-tag 0x20 0x40021c
.gnu.hash 0x1c 0x400240
.dynsym 0x108 0x400260
.dynstr 0x6d 0x400368
.gnu.version 0x16 0x4003d6
.gnu.version_r 0x20 0x4003f0
.rela.dyn 0x18 0x400410
.rela.plt 0xd8 0x400428
.init 0x18 0x400500
.plt 0xa0 0x400518
.text 0x488 0x4005c0
.fini 0xe 0x400a48
.rodata 0x25 0x400a58
.eh_frame_hdr 0x34 0x400a80
.eh_frame 0xd4 0x400ab8
.ctors 0x10 0x600b90
.dtors 0x10 0x600ba0
.jcr 0x8 0x600bb0
.dynamic 0x190 0x600bb8
.got 0x8 0x600d48
.got.plt 0x60 0x600d50
.data 0xa0 0x600dc0
.bss 0xa0 0x600e60
.comment 0x114 0x0
Total 0xde2
これらから次のようなメモリマップとなっていると考えられる。
+------------------------------+ 0x0000000000000000
: :
+------------------------------+ 0x0000000000400000
|text | 機械命令
| |
+------------------------------+ 0x0000000000401000
: :
+------------------------------+ 0x0000000000600000
| | 0x0000000000600dc0
|data | 初期化された static 変数
| initialized global var | 0x0000000000600de0
| initialized static var | 0x0000000000600e20
| |
+------------------------------+ 0x0000000000600e60
|BSS | 初期化されていない static 変数
| not initialized static var | 0x0000000000600e80
| not initialized global var | 0x0000000000600ec0
| |
+------------------------------+ 0x0000000000601000
: :
+------------------------------+ 0x0000000001f61000
|heap | malloc() で動的に確保される領域(上位アドレスに伸びる)
| malloc var | 0x0000000001f61010
| malloc var | 0x0000000001f61060
| |
+------------------------------+ 0x0000000001f82000
| |||| |
| VVVV |
: :
: :
| |
+------------------------------+ 0x00000037be000000
| |
| 共有ライブラリ |
| (ld-2.5.so, libc-2.5.so) |
+------------------------------+ 0x00000037be753000
| ??? |
+------------------------------+ 0x00000037be758000
| |
: :
: :
| ^^^^ ?? |
| |||| ?? |
+------------------------------+ 0x00007f2eee98d000
|heap?? |
| malloc var | 0x00007f2eee98f010
| big malloc var | 0x00007f2eee9b0010
| |
+------------------------------+ 0x00007f2eee9f3000
: :
+------------------------------+ 0x00007f2eeea0e000
|shared memory | 共有メモリ領域
| shared memory var | 0x00007f2eeea0e000
| |
+------------------------------+ 0x00007f2eeea0f000
|??? |
+------------------------------+ 0x00007f2eeea11000
| |
: :
: :
| ^^^^ |
| |||| |
+------------------------------+ 0x00007ffff7255000
|stack | 関数呼び出しやローカル変数等で使用されるスタック領域(下位アドレスに伸びる)
| local var | 0x00007ffff7269680
| arguments[1] | 0x00007ffff7269cb5
| |
+------------------------------+ 0x00007ffff726a000
: :
+------------------------------+ 0x00007ffff730e000
|??? |
+------------------------------+ 0x00007ffff730f000
| |
: :
: :
| |
+------------------------------+ 0xffffffffff600000
|arguments / environments?? |
| |
+------------------------------+ 0xffffffffff601000
: :
: :
+------------------------------+ 0xffffffffffffffff = 2^64 (64bit の場合)
heap としては 0x0000000001f61000 〜 0x0000000001f82000 の 132Kbytes が割り当てられているようだが、 約132Kbytes より大きく malloc() で動的にメモリを割り当てるとアドレスが飛んで 0x00007f2eee98d000 付近に メモリが割り当てられ、しかも下位にメモリが伸びているようだ。
別環境で採取した core ファイルを解析する。
別環境で発生した core ファイルを解析するためには以下のファイルが必要。
例えば、以下のように abort()
でわざと core を吐かせて試してみる。
$ cat hello.c
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
printf("hello, world\n");
abort();
return 0;
}
$ gcc -o hello hello.c
$ ./hello
hello, world
Aborted (core dumped)
$ ls -ltr | tail -1
-rw------- 1 oracle oinstall 184320 Oct 10 20:59 core.7441
core.7441 という core ファイルが吐かれた。
実行ファイルは core ファイルに対して file
コマンドを実行すれば確認できる。
$ file core.7441
core.7441: ELF 64-bit LSB core file AMD x86-64, version 1 (SYSV), SVR4-style, from 'hello'
hello
という実行ファイルによってこの core が吐かれたことが分かる。
共有ライブラリは GDB で core を読み込んで info share
で確認できる。
$ gdb ./hello ./core.7441
GNU gdb (GDB) Red Hat Enterprise Linux (7.0.1-42.el5)
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /tmp/hello...(no debugging symbols found)...done.
[New Thread 7441]
Reading symbols from /lib64/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib64/libc.so.6
Reading symbols from /lib64/ld-linux-x86-64.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib64/ld-linux-x86-64.so.2
Core was generated by `./hello'.
Program terminated with signal 6, Aborted.
#0 0x00000037be430265 in raise () from /lib64/libc.so.6
(gdb) info share
From To Syms Read Shared Object Library
0x00000037be41d780 0x00000037be50ad68 Yes (*) /lib64/libc.so.6
0x00000037be000a70 0x00000037be01682e Yes (*) /lib64/ld-linux-x86-64.so.2
(*): Shared library is missing debugging information.
共有ライブラリとしては以下が読み込まれていることが分かる。
なお、実行中に動的リンクを行うようなプログラムでなければ、共有ライブラリは ldd
コマンドでも確認できる。
$ ldd ./hello
linux-vdso.so.1 => (0x00007fffcd1ff000)
libc.so.6 => /lib64/libc.so.6 (0x00000037be400000)
/lib64/ld-linux-x86-64.so.2 (0x00000037be000000)
以上より、この例では他の環境でこの core ファイルを解析するためには次のファイルを採取する必要がある。
別環境に先ほど採取したファイルを展開する。 今回は以下のように配置した。
/tmp/core.7441
/tmp/bin/hello
/tmp/lib/libc.so.6
/tmp/lib/ld-linux-x86-64.so.2
GDB でこの core を解析するためには、採取した共有ライブラリを読み込むために以下のように solib-absolute-prefix
, solib-search-path
を設定する。
$ cd /tmp
$ gdb
(gdb) set solib-absolute-prefix /tmp/lib
(gdb) set solib-search-path /tmp/lib
(gdb) file ./bin/hello
(gdb) core-file ./core.7441
実際の実行例は以下。
$ gdb
GNU gdb (GDB) Red Hat Enterprise Linux (7.0.1-42.el5)
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
(gdb) set solib-absolute-prefix /tmp/lib
(gdb) set solib-search-path /tmp/lib
(gdb) file ./bin/hello
Reading symbols from /tmp/bin/hello...(no debugging symbols found)...done.
(gdb) core-file ./core.7441
[New Thread 7441]
Reading symbols from /tmp/lib/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /tmp/lib/libc.so.6
Reading symbols from /tmp/lib/ld-linux-x86-64.so.2...(no debugging symbols found)...done.
Loaded symbols for /tmp/lib/ld-linux-x86-64.so.2
Core was generated by `./hello'.
Program terminated with signal 6, Aborted.
#0 0x00000037be430265 in raise () from /tmp/lib/libc.so.6
(gdb) bt
#0 0x00000037be430265 in raise () from /tmp/lib/libc.so.6
#1 0x00000037be431d10 in abort () from /tmp/lib/libc.so.6
#2 0x00000000004004f6 in main ()
デバックオプション(-g オプション)付きでコンパイルされている場合は、ソースファイルがあればソースコードを使用した調査ができる。
コンパイルした環境と別の環境で core 解析する場合、ソースコードを入手して、GDB の directory
にてソースファイルを展開したディレクトリを指定する。
実行例は以下。(実行ファイルはデバックオプション付きでコンパイルしたものを使用)
(gdb) bt
#0 0x00000030ea830265 in raise () from /tmp/lib/libc.so.6
#1 0x00000030ea831d10 in abort () from /tmp/lib/libc.so.6
#2 0x00000000004004f6 in main (argc=1, argv=0x7fff9d995df8) at hello.c:7
(gdb) frame 2
#2 0x00000000004004f6 in main (argc=1, argv=0x7fff9d995df8) at hello.c:7
7 hello.c: No such file or directory.
in hello.c
(gdb) list
2 in hello.c
ソースファイルが見つからないと上記のようにソースコードが必要な調査ができない。
directory
にてソースファイルを展開したディレクトリを指定すると、ソースコードを元にした調査ができる。
(ディレクトリが複数ある場合は:
で区切って複数指定するか、directory
をディレクトリの分だけ実行すれば追加される)
(gdb) directory /tmp/src
Source directories searched: /tmp/src:$cdir:$cwd
(gdb) frame 2
#2 0x00000000004004f6 in main (argc=1, argv=0x7fff9d995df8) at hello.c:7
7 abort();
(gdb) list
2 #include <stdlib.h>
3
4 int main(int argc, char *argv[])
5 {
6 printf("hello, world\n");
7 abort();
8 return 0;
9 }
]]>SQL 計画管理により、実行計画を固定化する。
SQL 計画管理により、実行計画を固定化することができる。具体的には次のステップで行う。
OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES=TRUE
)OPTIMIZER_USE_SQL_PLAN_BASELINES=TRUE
)実際に実行してみる。
OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES=TRUE
)OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES
初期化パラメータ(デフォルト FALSE
)を TRUE
に設定することで SQL 計画の自動取得が有効になる。
SQL> ALTER SESSION SET OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES=TRUE;
Session altered.
SQL を 2 回以上実行して、SQL 計画ベースラインに保存する。
SQL> SELECT * FROM scott.emp WHERE empno = 7900;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7900 JAMES CLERK 7698 81-12-03 960 30
SQL> SELECT * FROM scott.emp WHERE empno = 7900;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7900 JAMES CLERK 7698 81-12-03 960 30
OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES=FALSE
)SQL 計画の自動取得を無効化する。
SQL> ALTER SESSION SET OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES=FALSE;
Session altered.
DBA_SQL_PLAN_BASELINES
を確認する。
SQL> col sql_text for a60
col sql_handle for a30
col plan_name for a30
set lines 200
SQL> SELECT SQL_TEXT, SQL_HANDLE, PLAN_NAME, ENABLED, ACCEPTED, FIXED
FROM DBA_SQL_PLAN_BASELINES;
SQL_TEXT SQL_HANDLE PLAN_NAME ENABLED ACCEPTED FIXED
------------------------------------------------------------ ------------------------------ ------------------------------ --------- --------- ---------
SELECT * FROM scott.emp WHERE empno = 7900 SQL_84ec680ef31d6de4 SQL_PLAN_89v381vtjuvg4695cc014 YES YES NO
実行計画を確認する。
SQL> SELECT * FROM TABLE(
DBMS_XPLAN.DISPLAY_SQL_PLAN_BASELINE(
sql_handle=>'SQL_84ec680ef31d6de4',
format=>'basic'));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
SQL handle: SQL_84ec680ef31d6de4
SQL text: SELECT * FROM scott.emp WHERE empno = 7900
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Plan name: SQL_PLAN_89v381vtjuvg4695cc014 Plan id: 1767686164
Enabled: YES Fixed: NO Accepted: YES Origin: AUTO-CAPTURE
--------------------------------------------------------------------------------
PLAN_TABLE_OUTPUT
----------------------------------------------
Plan hash value: 2949544139
----------------------------------------------
| Id | Operation | Name |
----------------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | TABLE ACCESS BY INDEX ROWID| EMP |
| 2 | INDEX UNIQUE SCAN | PK_EMP |
----------------------------------------------
20 rows selected.
OPTIMIZER_USE_SQL_PLAN_BASELINES=TRUE
)OPTIMIZER_USE_SQL_PLAN_BASELINES
初期化パラメータ(デフォルト TRUE
)を TRUE
に設定することで取得した SQL 計画を使用する。
SQL> ALTER SESSION SET OPTIMIZER_USE_SQL_PLAN_BASELINES=TRUE;
Session altered.
あとは普通に SQL を実行するだけで取得した SQL 計画が使用される。
SQL> set autotrace on
SQL> SELECT * FROM scott.emp WHERE empno = 7900;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ------------------------------ --------------------------- ---------- -------- ---------- ---------- ----------
7900 JAMES CLERK 7698 81-12-03 960 30
Execution Plan
----------------------------------------------------------
Plan hash value: 2949544139
--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 39 | 1 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 39 | 1 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | PK_EMP | 1 | | 0 (0)| 00:00:01 |
--------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("EMPNO"=7900)
Note
-----
- SQL plan baseline "SQL_PLAN_89v381vtjuvg4695cc014" used for this statement
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
2 consistent gets
0 physical reads
0 redo size
889 bytes sent via SQL*Net to client
512 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
Note の SQL plan baseline "SQL_PLAN_89v381vtjuvg4695cc014" used for this statement
という出力から取得した SQL 計画が使用されていることが分かる。
カーソルを無効化せずに統計情報を取得する。
カーソルを無効化せずに統計情報を取得する。 方法としては、以下の2つがある。
DBMS_STATS.GATHER_TABLE_STATS
プロシージャ実行時に指定する方法SET_*_PREFS
プロシージャによりデフォルト値を変更する方法DBMS_STATS.GATHER_TABLE_STATS
プロシージャ実行時に指定する方法DBMS_STATS.GATHER_TABLE_STATS
実行時に指定する場合は、no_invalidate
パラメータを TRUE
にする。
SQL> EXEC DBMS_STATS.GATHER_TABLE_STATS('scott', 'emp', no_invalidate => TRUE);
PL/SQL procedure successfully completed.
SET_*_PREFS
プロシージャによりデフォルト値を変更する方法表単位でデフォルト値を変更する場合は SET_TABLE_PREFS
プロシージャを使う。
GET_PREFS
プロシージャで現在の設定の確認。
SQL> SELECT DBMS_STATS.GET_PREFS('NO_INVALIDATE', 'scott', 'emp') FROM DUAL;
DBMS_STATS.GET_PREFS('NO_INVALIDATE','SCOTT','EMP')
---------------------------------------------------
DBMS_STATS.AUTO_INVALIDATE
SET_TABLE_PREFS
プロシージャにて設定の変更。
SQL> EXEC DBMS_STATS.SET_TABLE_PREFS('scott', 'emp', 'NO_INVALIDATE', 'TRUE');
PL/SQL procedure successfully completed.
GET_PREFS
プロシージャで設定が変更されたことを確認。
SQL> SELECT DBMS_STATS.GET_PREFS('NO_INVALIDATE', 'scott', 'emp') FROM DUAL;
DBMS_STATS.GET_PREFS('NO_INVALIDATE','SCOTT','EMP')
---------------------------------------------------
TRUE
あとは no_invalidate
オプションを明示的に指定せずに統計情報を採取すると変更した内容がデフォルトとして採用される。
SQL> EXEC DBMS_STATS.GATHER_TABLE_STATS('scott', 'emp');
PL/SQL procedure successfully completed.
]]>相関のある複数列を列グループとして統計情報採取することでよりよい実行計画となるようにする。
相関のある複数列を列グループとして統計情報を採取することで、オプティマイザが個々の列でなく列グループとして実行計画を計算するようにする。
C1 列、C2 列が同じになる(つまり相関がある)ような表を作成する。
SQL> CREATE TABLE multi_col_tab (c1 NUMBER, c2 NUMBER);
Table created.
SQL> BEGIN
FOR i IN 1..5 LOOP
FOR j IN 1..100 LOOP
INSERT INTO multi_col_tab VALUES(i, i);
END LOOP;
END LOOP;
COMMIT;
END;
/
PL/SQL procedure successfully completed.
1 〜 5 までそれぞれ 100 行ずつ、計 500 行を INSERT。
複数列統計を採取しない場合の実行計画
SQL> BEGIN
DBMS_STATS.GATHER_TABLE_STATS(null,'MULTI_COL_TAB',
METHOD_OPT => 'FOR ALL COLUMNS SIZE SKEWONLY');
END;
/
PL/SQL procedure successfully completed.
SQL> set autotrace on
SQL> SELECT COUNT(*) FROM multi_col_tab WHERE c1 = 1 AND c2 = 1;
COUNT(*)
----------
100
Execution Plan
----------------------------------------------------------
Plan hash value: 610516676
------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 6 | 3 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 6 | | |
|* 2 | TABLE ACCESS FULL| MULTI_COL_TAB | 20 | 120 | 3 (0)| 00:00:01 |
------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("C1"=1 AND "C2"=1)
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
6 consistent gets
0 physical reads
0 redo size
526 bytes sent via SQL*Net to client
523 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
実行計画の MULTI_COL_TAB
の出力行に注目
------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 6 | 3 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 6 | | |
|* 2 | TABLE ACCESS FULL| MULTI_COL_TAB | 20 | 120 | 3 (0)| 00:00:01 |
------------------------------------------------------------------------------------
Rows
が 20 となっている。
条件が WHERE c1 = 1 AND c2 = 1
だが、相関があることを知らないので、選択行が 500行 * 1/5(c1 の選択率) * 1/5(c2 の選択率)
= 20行 となっていて正確でない。
複数列統計を採取する。c1
および c2
列で構成させる列グループを追加して、統計情報取得。
SQL> DECLARE
cg_name varchar2(30);
BEGIN
cg_name := dbms_stats.create_extended_stats(null,'multi_col_tab',
'(c1,c2)');
END;
/
PL/SQL procedure successfully completed.
SQL> BEGIN
DBMS_STATS.GATHER_TABLE_STATS(null,'MULTI_COL_TAB',
METHOD_OPT => 'FOR ALL COLUMNS SIZE SKEWONLY');
END;
/
PL/SQL procedure successfully completed.
なお、DBMS_STATS.CREATE_EXTENDED_STATS
で明示的に列グループを作成しない場合は、以下のように統計情報収集時に METHOD_OPT
で複数列を指定してもよい。
SQL> BEGIN
DBMS_STATS.GATHER_TABLE_STATS(null,'MULTI_COL_TAB',
METHOD_OPT => 'FOR ALL COLUMNS SIZE SKEWONLY FOR COLUMNS (C1,C2) SIZE SKEWONLY');
END;
/
PL/SQL procedure successfully completed.
先ほどと同じ SQL で実行計画を確認してみる。
SQL> set autotrace on
SQL> SELECT COUNT(*) FROM multi_col_tab WHERE c1 = 1 AND c2 = 1;
COUNT(*)
----------
100
Execution Plan
----------------------------------------------------------
Plan hash value: 610516676
------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 6 | 3 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 6 | | |
|* 2 | TABLE ACCESS FULL| MULTI_COL_TAB | 100 | 600 | 3 (0)| 00:00:01 |
------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("C1"=1 AND "C2"=1)
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
6 consistent gets
0 physical reads
0 redo size
526 bytes sent via SQL*Net to client
523 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
実行計画の MULTI_COL_TAB
の出力行に注目
------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 6 | 3 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 6 | | |
|* 2 | TABLE ACCESS FULL| MULTI_COL_TAB | 100 | 600 | 3 (0)| 00:00:01 |
------------------------------------------------------------------------------------
Rows
が 100 となっている。
条件が WHERE c1 = 1 AND c2 = 1
で相関があることを認識して正確に 100 行と計算している。
拡張統計として、複数列が認識されていることを確認。
SQL> col extension_name for a30
col extension for a30
SQL> Select extension_name, extension
from user_stat_extensions
where table_name='MULTI_COL_TAB';
EXTENSION_NAME EXTENSION
------------------------------ ------------------------------
SYS_STUF3GLKIOP5F4B0BTTCFTMX0W ("C1","C2")
実際に複数列の統計が採られていることを確認
SQL> col col_group for a30
SQL> select e.extension col_group, t.num_distinct, t.histogram
from user_stat_extensions e, user_tab_col_statistics t
where e.extension_name=t.column_name
and e.table_name=t.table_name
and t.table_name='MULTI_COL_TAB';
COL_GROUP NUM_DISTINCT HISTOGRAM
------------------------------ ------------ ---------------------------------------------
("C1","C2") 5 FREQUENCY
HISTGRAM
列が NONE
以外であればヒストグラムが採られている。
“結果キャッシュ” 機能を使用する。
“結果キャッシュ” 機能を使用することで、問い合わせ結果を再利用してパフォーマンスを向上させることができる。
結果キャッシュを有効にするためには初期化パラメータ RESULT_CACHE_MAX_SIZE
を 0 より大きくする必要がある。
0 の場合は結果キャッシュは無効となる。(以下のように DBMS_RESULT_CACHE.STATUS
の結果が DISABLED
となっている。)
SQL> show parameter result_cache_max_size
NAME TYPE VALUE
------------------------------------ --------------------------------- ------------------------------
result_cache_max_size big integer 0
SQL> SELECT DBMS_RESULT_CACHE.STATUS FROM DUAL;
STATUS
----------
DISABLED
初期化パラメータ RESULT_CACHE_MAX_SIZE
は以下のように動的に変更できるが、実際はインスタンスを再起動しないと有効にならなかった。
SQL> ALTER SYSTEM SET result_cache_max_size = 15M SCOPE=both;
System altered.
SQL> show parameter result_cache_max_size
NAME TYPE VALUE
------------------------------------ --------------------------------- ------------------------------
result_cache_max_size big integer 15M
SQL> SELECT DBMS_RESULT_CACHE.STATUS FROM DUAL;
STATUS
----------
DISABLED
インスタンスを再起動すると、有効になり、DBMS_RESULT_CACHE.STATUS
も ENABLED
に変わる。
SQL> shutdown immediate
Database closed.
Database dismounted.
ORACLE instance shut down.
SQL> startup
ORACLE instance started.
Total System Global Area 835104768 bytes
Fixed Size 2232960 bytes
Variable Size 734006656 bytes
Database Buffers 92274688 bytes
Redo Buffers 6590464 bytes
Database mounted.
Database opened.
SQL> show parameter result_cache_max_size
NAME TYPE VALUE
------------------------------------ --------------------------------- ------------------------------
result_cache_max_size big integer 15M
SQL> SELECT DBMS_RESULT_CACHE.STATUS FROM DUAL;
STATUS
----------
ENABLED
結果キャッシュを使用するには /*+ RESULT_CACHE */
ヒントを付与して問い合わせを実行すればよい。
SQL> SELECT /*+ RESULT_CACHE */ AVG(sal) FROM scott.emp;
実行計画で、RESULT CACHE
と出ていると結果キャッシュが使われている。
SQL> set lines 200
SQL> set autotrace on
SQL> SELECT /*+ RESULT_CACHE */ AVG(sal) FROM scott.emp;
AVG(SAL)
----------
2083.21429
Execution Plan
----------------------------------------------------------
Plan hash value: 2083865914
--------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 4 | 3 (0)| 00:00:01 |
| 1 | RESULT CACHE | 4z1ag1pqa9zqm704zf4nuj9tq4 | | | | |
| 2 | SORT AGGREGATE | | 1 | 4 | | |
| 3 | TABLE ACCESS FULL| EMP | 14 | 56 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------
Result Cache Information (identified by operation id):
------------------------------------------------------
1 - column-count=1; dependencies=(SCOTT.EMP); attributes=(single-row); name="SELECT /*+ RESULT_CACHE */ AVG(sal) FROM scott.emp"
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
0 consistent gets
0 physical reads
0 redo size
545 bytes sent via SQL*Net to client
523 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
また、V$RESULT_CACHE_OBJECTS
からも結果キャッシュについて確認できる。
SQL> SELECT type, status, name FROM v$result_cache_objects;
TYPE STATUS
------------------------------ ---------------------------
NAME
--------------------------------------------------------------------------------
Dependency Published
SCOTT.EMP
Result Published
SELECT /*+ RESULT_CACHE */ AVG(sal) FROM scott.emp
]]>フラッシュバック・データ・アーカイブ(Oracle Total Recall) を使用して、表への更新履歴を長期間保存する。
フラッシュバック・データ・アーカイブ(Oracle Total Recall) を使用して、表への更新履歴を長期間保存できる。例えば、コンプライアンス上変更履歴を年単位で長時間保存しておく必要がある場合などに有効。
CREATE FLASHBACK ARCHIVE
文を使用してフラッシュバック・データ・アーカイブを作成する。
データを1年間保持するデフォルトのフラッシュバック・データ・アーカイブ fla1
を USERS
表領域に作成。
作成するには FLASHBACK ARCHIVE ADMINISTER
システム権限を持つユーザか、SYSDBA として接続する必要がある。
SQL> connect /as sysdba
Connected.
SQL> CREATE FLASHBACK ARCHIVE DEFAULT fla1 TABLESPACE users
RETENTION 1 YEAR;
Flashback archive created.
scott.emp
表のフラッシュバック・アーカイブを有効にして、フラッシュバック・データ・アーカイブ fla1
に履歴データを格納するようにする。
scott
スキーマに fla1
に対する FLASHBACK ARCHIVE
オブジェクト権限を与えて、ALTER TABLE
文を発行する。
SQL> connect /as sysdba
Connected.
SQL> GRANT FLASHBACK ARCHIVE ON fla1 TO scott;
Grant succeeded.
SQL> connect scott/tiger
Connected.
SQL> ALTER TABLE scott.emp FLASHBACK ARCHIVE fla1;
Table altered.
scott
スキーマで emp
表のフラッシュバック・データ・アーカイブが有効になっていることの確認
SQL> connect scott/tiger
Connected.
SQL> col table_name for a30
col owner_name for a30
col flashback_archive_name for a30
col archive_table_name for a30
col status for a10
set lines 200
SQL> SELECT * FROM USER_FLASHBACK_ARCHIVE_TABLES;
TABLE_NAME OWNER_NAME FLASHBACK_ARCHIVE_NAME ARCHIVE_TABLE_NAME STATUS
------------------------------ ------------------------------ ------------------------------ ------------------------------ ----------
EMP SCOTT FLA1 SYS_FBA_HIST_75335 ENABLED
]]>時間隔パーティション表を作成する。
時間隔パーティション表はレンジ・パーティションの一種で、例えば月単位の時間隔パーティション表を作成すると、パーティションを明示的に作成しなくても自動で必要な月のパーティションをデータ insert 時に作成してくれる。
DATE
型の time_id
をパーティション・キーとして、2009年までは年単位でパーティション化し、2010年1月1日以降は月単位でパーティションを自動作成するような時間隔パーティション表を作成してみる。
SQL> CREATE TABLE interval_sales
( prod_id NUMBER(6)
, cust_id NUMBER
, time_id DATE
, channel_id CHAR(1)
, promo_id NUMBER(6)
, quantity_sold NUMBER(3)
, amount_sold NUMBER(10,2)
)
PARTITION BY RANGE (time_id)
INTERVAL(NUMTOYMINTERVAL(1, 'MONTH'))
( PARTITION p0 VALUES LESS THAN (TO_DATE('1-1-2008', 'DD-MM-YYYY')),
PARTITION p1 VALUES LESS THAN (TO_DATE('1-1-2009', 'DD-MM-YYYY')),
PARTITION p2 VALUES LESS THAN (TO_DATE('1-1-2010', 'DD-MM-YYYY')) );
Table created.
ちなみに追加されるパーティションを日単位にする場合は、NUMTODSINTERVAL(1, 'DAY')
関数を利用する。
パーティション情報の確認。
SQL> col table_name for a30
col partitioning_type for a20
set lines 200
SQL> SELECT table_name, partitioning_type FROM user_part_tables;
TABLE_NAME PARTITIONING_TYPE
------------------------------ --------------------
INTERVAL_SALES RANGE
SQL> col partition_name for a30
SQL> SELECT table_name, partition_name, high_value FROM user_tab_partitions ORDER BY table_name, partition_position;
TABLE_NAME PARTITION_NAME HIGH_VALUE
------------------------------ ------------------------------ --------------------------------------------------------------------------------
INTERVAL_SALES P0 TO_DATE(' 2008-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
INTERVAL_SALES P1 TO_DATE(' 2009-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
INTERVAL_SALES P2 TO_DATE(' 2010-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
最初はパーティションは作られていないが、上記の範囲を超える行を insert するとパーティションが自動で作成される。
SQL> INSERT INTO interval_sales(prod_id, time_id) VALUES(1, TO_DATE('2012-09-16', 'YYYY-MM-DD'));
1 row created.
SQL> COMMIT;
Commit complete.
SQL> SELECT table_name, partition_name, high_value FROM user_tab_partitions ORDER BY table_name, partition_position;
TABLE_NAME PARTITION_NAME HIGH_VALUE
------------------------------ ------------------------------ --------------------------------------------------------------------------------
INTERVAL_SALES P0 TO_DATE(' 2008-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
INTERVAL_SALES P1 TO_DATE(' 2009-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
INTERVAL_SALES P2 TO_DATE(' 2010-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
INTERVAL_SALES SYS_P41 TO_DATE(' 2012-10-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
SYS_P41
というパーティションが自動で作成された。
参照パーティション表を作成する。
参照パーティション表は、例えば次のようなケースで役立つ。注文を管理する orders 表に注文日が格納される order_date 列がある。また、注文に含まれる品目を管理する order_items 表があり、orders 表を order_id 列で外部参照しているとする。orders 表が order_date 列をキー値としてパーティション化されている場合、order_items 表も同じように注文日によりパーティション化したかったら、従来ならば order_date 列を order_itmes 表にも加える必要があった。参照パーティション表を使えば、order_items 列に余計な order_date 列を加えることなく、外部参照している orders と同じ単位でパーティション化することができる。
実際に上記シナリオで参照パーティション表を作成してみる。
SQL> CREATE TABLE orders
( order_id NUMBER(12),
order_date DATE,
order_mode VARCHAR2(8),
customer_id NUMBER(6),
order_status NUMBER(2),
order_total NUMBER(8,2),
sales_rep_id NUMBER(6),
promotion_id NUMBER(6),
CONSTRAINT orders_pk PRIMARY KEY(order_id)
)
PARTITION BY RANGE(order_date)
( PARTITION Q1_2005 VALUES LESS THAN (TO_DATE('01-APR-2005','DD-MON-YYYY')),
PARTITION Q2_2005 VALUES LESS THAN (TO_DATE('01-JUL-2005','DD-MON-YYYY')),
PARTITION Q3_2005 VALUES LESS THAN (TO_DATE('01-OCT-2005','DD-MON-YYYY')),
PARTITION Q4_2005 VALUES LESS THAN (TO_DATE('01-JAN-2006','DD-MON-YYYY'))
);
Table created.
SQL> CREATE TABLE order_items
( order_id NUMBER(12) NOT NULL,
line_item_id NUMBER(3) NOT NULL,
product_id NUMBER(6) NOT NULL,
unit_price NUMBER(8,2),
quantity NUMBER(8),
CONSTRAINT order_items_fk
FOREIGN KEY(order_id) REFERENCES orders(order_id)
)
PARTITION BY REFERENCE(order_items_fk);
Table created.
パーティション情報の確認。
SQL> col table_name for a30
col partitioning_type for a20
col ref_ptn_constraint_name for a30
set lines 200
SQL> SELECT table_name, partitioning_type, ref_ptn_constraint_name FROM user_part_tables;
TABLE_NAME PARTITIONING_TYPE REF_PTN_CONSTRAINT_NAME
------------------------------ -------------------- ------------------------------
ORDERS RANGE
ORDER_ITEMS REFERENCE ORDER_ITEMS_FK
SQL> col partition_name for a30
SQL> SELECT table_name, partition_name, high_value FROM user_tab_partitions ORDER BY table_name, partition_position;
TABLE_NAME PARTITION_NAME HIGH_VALUE
------------------------------ ------------------------------ --------------------------------------------------------------------------------
ORDERS Q1_2005 TO_DATE(' 2005-04-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
ORDERS Q2_2005 TO_DATE(' 2005-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
ORDERS Q3_2005 TO_DATE(' 2005-10-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
ORDERS Q4_2005 TO_DATE(' 2006-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIA
ORDER_ITEMS Q1_2005
ORDER_ITEMS Q2_2005
ORDER_ITEMS Q3_2005
ORDER_ITEMS Q4_2005
]]>従来型の LOB (BasicFiles LOB) から、Oracle Database 11g からの新しい LOB アーキテクチャである SecureFiles を使用した LOB へ移行する。
SecureFilesおよびラージ・オブジェクト開発者ガイド
–> 4 Oracle SecureFiles LOBの使用
–> BasicFiles LOBからSecureFiles LOBへの列の移行
SecureFiles LOBの初期化パラメータdb_securefile
SecureFiles LOBを含んだCREATE TABLEの使用
SecureFiles LOBを含んだALTER TABLEの使用
管理者ガイド
–> 20 表の管理
–> 表のオンライン再定義
Oracle Database 11g からの新しい LOB アーキテクチャである SecureFiles を使用した LOB を使用すると、以下のようなことが可能になる。
従来型の LOB は、BasicFiles と呼ばれる。BasicFiles を使用した既存の LOB 列を SecureFiles に移行する。ALTER TABLE
での BasicFiles から SecureFiles への変更はできないため、変更するためには表の再作成を行うか、オンライン再定義を行う必要がある。今回は、マニュアルに例があるように、オンライン再定義により行う。
BasicFiles LOB 列を持つ cust
表を作成してデータを insert。今回この表を SecureFiles LOB に移行してみる。
SQL> connect scott/tiger
SQL> CREATE TABLE cust
(
c_id NUMBER PRIMARY KEY,
c_zip NUMBER,
c_name VARCHAR(30) DEFAULT NULL,
c_lob CLOB
);
Table created.
SQL> INSERT INTO cust VALUES(1, 94065, 'hhh', 'ttt');
1 row created.
SQL> COMMIT;
Commit complete.
オンライン再定義に必要な権限を与える
SQL> connect /as sysdba
SQL> -- Grant privileges required for online redefinition.
SQL> GRANT EXECUTE ON DBMS_REDEFINITION TO scott;
SQL> GRANT ALTER ANY TABLE TO scott;
SQL> GRANT DROP ANY TABLE TO scott;
SQL> GRANT LOCK ANY TABLE TO scott;
SQL> GRANT CREATE ANY TABLE TO scott;
SQL> GRANT SELECT ANY TABLE TO scott;
SQL> -- Privileges required to perform cloning of dependent objects.
SQL> GRANT CREATE ANY TRIGGER TO scott;
SQL> GRANT CREATE ANY INDEX TO scott;
db_securefile
パラメータの変更SecureFiles LOB の初期化パラメータ db_securefile
パラメータを変更する。
SecureFiles LOB を使用するためには PERMITTED
(デフォルト) または ALWAYS
である必要がある。
例えば、NEVER
に設定されている場合に SecureFiles LOB の列を作成しようとすると、ORA-43856
が発生する。
SQL> CREATE TABLE test_lob (c_lob CLOB) LOB(c_lob) STORE AS SECUREFILE (compress high);
CREATE TABLE test_lob (c_lob CLOB) LOB(c_lob) STORE AS SECUREFILE (compress high)
*
ERROR at line 1:
ORA-43856: Unsupported LOB type for SECUREFILE LOB operation
db_securefile
を ALTER SYSTEM
で変更する。
SQL> conn /as sysdba
Connected.
SQL> ALTER SYSTEM SET db_securefile = PERMITTED SCOPE=both;
System altered.
オンライン再定義のための仮表を SecureFiles LOB 列を持つようにして作成。元表からコピーされるので主キーなどの制約の指定は不要。
SQL> connect scott/tiger
Connected.
SQL> CREATE TABLE cust_int
(
c_id NUMBER,
c_zip NUMBER,
c_name VARCHAR(30) DEFAULT NULL,
c_lob CLOB
)
LOB(c_lob) STORE AS SECUREFILE;
Table created.
表のオンライン再定義を実行する。
SQL> DECLARE
col_mapping VARCHAR2(1000);
BEGIN
-- map all the columns in the interim table to the original table
col_mapping :=
'c_id c_id , '||
'c_zip c_zip , '||
'c_name c_name, '||
'c_lob c_lob';
DBMS_REDEFINITION.START_REDEF_TABLE('scott', 'cust', 'cust_int', col_mapping);
END;
/
PL/SQL procedure successfully completed.
SQL> set serveroutput on
SQL> DECLARE
error_count pls_integer := 0;
BEGIN
DBMS_REDEFINITION.COPY_TABLE_DEPENDENTS('scott', 'cust', 'cust_int',
1, TRUE,TRUE,TRUE,FALSE, error_count);
DBMS_OUTPUT.PUT_LINE('errors := ' || TO_CHAR(error_count));
END;
/
errors := 0
PL/SQL procedure successfully completed.
SQL> EXEC DBMS_REDEFINITION.FINISH_REDEF_TABLE('scott', 'cust', 'cust_int');
PL/SQL procedure successfully completed.
SQL> DROP TABLE cust_int;
Table dropped.
USER_LOBS.SECUREFILE
列が YES
であれば SecureFiles になっている。
SQL> col table_name for a30
SQL> col column_name for a30
SQL> SELECT table_name, column_name, securefile FROM user_lobs;
TABLE_NAME COLUMN_NAME SECUREFIL
------------------------------ ------------------------------ ---------
CUST C_LOB YES
ALTER TABLE
文により既存の SecureFiles LOB 列を変更して、圧縮や重複除外の設定を行う。
USER_LOBS
にて確認できる。
SQL> col table_name for a30
col column_name for a30
col encrypt for a10
col compression for a15
col deduplication for a15
set lines 200
SQL> SELECT table_name, column_name, encrypt, compression, deduplication FROM user_lobs;
TABLE_NAME COLUMN_NAME ENCRYPT COMPRESSION DEDUPLICATION
------------------------------ ------------------------------ ---------- --------------- ---------------
CUST C_LOB NO NO NO
ALTER TABLE
により圧縮、重複除外の設定を行う。
SQL> ALTER TABLE cust MODIFY LOB(c_lob) (COMPRESS HIGH);
Table altered.
SQL> ALTER TABLE cust MODIFY LOB(c_lob) (DEDUPLICATE);
Table altered.
USER_LOBS
にて確認。
SQL> col table_name for a30
col column_name for a30
col encrypt for a10
col compression for a15
col deduplication for a15
set lines 200
SQL> SELECT table_name, column_name, encrypt, compression, deduplication FROM user_lobs;
TABLE_NAME COLUMN_NAME ENCRYPT COMPRESSION DEDUPLICATION
------------------------------ ------------------------------ ---------- --------------- ---------------
CUST C_LOB NO HIGH LOB
]]>スター型変換を行い、スター・クエリーをチューニングする。
スター型変換を行い、スター・クエリーをチューニングする。 スター・クエリーは1つの大規模なファクト表と、複数の小規模なディメンション表を結合するようなクエリー。ファクト表が大きいため、ファクト表をなるべく条件を絞り込んだ後でアクセスするような特別な結合(スター結合)をしたほうが効率がよい。このために暗黙的に SQL をリライト(または変換)する機能をスター型変換という。
スター型変換を行うためには、以下に従う必要がある。
STAR_TRANSFORMATION_ENABLED
を TRUE
に設定する必要がある。検証してみる。
ディメンション表 jobs
, dates
, ファクト表 emp_fact
を作成
$ sqlplus scott/tiger
SQL> -- ディメンション表 jobs
SQL> CREATE TABLE jobs
AS SELECT rownum job_id, job
FROM (SELECT DISTINCT job FROM emp);
Table created.
SQL> ALTER TABLE jobs ADD (CONSTRAINT jobs_pk PRIMARY KEY (job_id) VALIDATE);
Table altered.
SQL> -- ディメンション表 dates
SQL> CREATE TABLE dates
AS SELECT rownum date_id, hiredate, to_char(hiredate, 'YYYY') year
FROM (SELECT DISTINCT hiredate FROM emp);
Table created.
SQL> ALTER TABLE dates ADD (CONSTRAINT dates_pk PRIMARY KEY (date_id) VALIDATE);
Table altered.
SQL> -- ファクト表 emp_fact
SQL> CREATE TABLE emp_fact
AS SELECT e.empno, e.ename, j.job_id, e.mgr, d.date_id, e.sal, e.comm, e.deptno
FROM emp e, dates d, jobs j
WHERE e.job = j.job AND e.hiredate = d.hiredate;
Table created.
SQL> ALTER TABLE emp_fact ADD (CONSTRAINT emp_fact_jobs_fk FOREIGN KEY(job_id) REFERENCES jobs(job_id) VALIDATE);
Table altered.
SQL> ALTER TABLE emp_fact ADD (CONSTRAINT emp_fact_dates_fk FOREIGN KEY(date_id) REFERENCES dates(date_id) VALIDATE);
Table altered.
SQL> -- データロード
SQL> INSERT INTO emp_fact SELECT * FROM emp_fact;
14 rows created.
SQL> /
28 rows created.
...(以下繰り返す)
SQL> /
114688 rows created.
SQL> COMMIT;
Commit complete.
SQL> -- 統計情報取得
SQL> EXEC DBMS_STATS.GATHER_SCHEMA_STATS('scott');
PL/SQL procedure successfully completed.
スター変換されているかどうかは set autotrace trace exp
等の実行計画から確認する。
例えば set autotrace trace exp
で Note
に以下の出力があればスター変換されている。
Note
-----
- star transformation used for this statement
ちなみに set autotrace
を使用するためには PLUSTRACE
ロールが必要で、以下のように作成する。
SQL> connect /as sysdba
Connected.
SQL> @?/sqlplus/admin/plustrce.sql
SQL> GRANT plustrace TO scott;
Grant succeeded.
以下の SELECT 文で試してみる。
SQL> set timing on
SQL> set autotrace on
SQL> set lines 200
SQL> SELECT j.job, d.year, SUM(e.sal), d.dname
FROM emp_fact e, jobs j, dates d, dept d
WHERE e.job_id = j.job_id
AND e.date_id = d.date_id
AND e.deptno = d.deptno
AND j.job = 'CLERK'
AND d.year in ('1980', '1987')
GROUP BY j.job, d.year, d.dname;
JOB YEAR SUM(E.SAL) DNAME
--------------------------- ------------ ---------- ------------------------------------------
CLERK 1987 18022400 RESEARCH
CLERK 1980 13107200 RESEARCH
Elapsed: 00:00:00.04
Execution Plan
----------------------------------------------------------
Plan hash value: 3373499036
------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 180 | 324 (3)| 00:00:04 |
| 1 | HASH GROUP BY | | 4 | 180 | 324 (3)| 00:00:04 |
|* 2 | HASH JOIN | | 22938 | 1008K| 323 (2)| 00:00:04 |
| 3 | TABLE ACCESS FULL | DEPT | 4 | 52 | 3 (0)| 00:00:01 |
|* 4 | HASH JOIN | | 22938 | 716K| 319 (2)| 00:00:04 |
| 5 | MERGE JOIN CARTESIAN| | 7 | 133 | 6 (0)| 00:00:01 |
|* 6 | TABLE ACCESS FULL | JOBS | 1 | 11 | 3 (0)| 00:00:01 |
| 7 | BUFFER SORT | | 7 | 56 | 3 (0)| 00:00:01 |
|* 8 | TABLE ACCESS FULL | DATES | 7 | 56 | 3 (0)| 00:00:01 |
| 9 | TABLE ACCESS FULL | EMP_FACT | 229K| 2912K| 312 (2)| 00:00:04 |
------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("E"."DEPTNO"="D"."DEPTNO")
4 - access("E"."JOB_ID"="J"."JOB_ID" AND "E"."DATE_ID"="D"."DATE_ID")
6 - filter("J"."JOB"='CLERK')
8 - filter("D"."YEAR"='1980' OR "D"."YEAR"='1987')
Statistics
----------------------------------------------------------
0 recursive calls
1 db block gets
1098 consistent gets
0 physical reads
0 redo size
824 bytes sent via SQL*Net to client
524 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
2 rows processed
0.04 秒かかっている。
スター型変換するようにビットマップ索引を作成し、初期化パラメータALTER SESSION SET STAR_TRANSFORMATION_ENABLED
を TRUE
にして再実行する。
SQL> -- ビットマップ索引を作成
SQL> CREATE BITMAP INDEX emp_fact_job_bix ON emp_fact(job_id);
Index created.
SQL> CREATE BITMAP INDEX emp_fact_date_bix ON emp_fact(date_id);
Index created.
SQL> CREATE BITMAP INDEX emp_fact_dept_bix ON emp_fact(deptno);
Index created.
SQL> -- 統計情報採取
SQL> EXEC DBMS_STATS.GATHER_SCHEMA_STATS('scott');
PL/SQL procedure successfully completed.
SQL> -- 初期化パラメータ変更
SQL> SQL> ALTER SESSION SET STAR_TRANSFORMATION_ENABLED = TRUE
Session altered.
先ほどと同じ SELECT を実行する。
SQL> set timing on
SQL> set autotrace on
SQL> set lines 200
SQL> SELECT j.job, d.year, SUM(e.sal), d.dname
FROM emp_fact e, jobs j, dates d, dept d
WHERE e.job_id = j.job_id
AND e.date_id = d.date_id
AND e.deptno = d.deptno
AND j.job = 'CLERK'
AND d.year in ('1980', '1987')
GROUP BY j.job, d.year, d.dname;
JOB YEAR SUM(E.SAL) DNAME
--------------------------- ------------ ---------- ------------------------------------------
CLERK 1987 18022400 RESEARCH
CLERK 1980 13107200 RESEARCH
Elapsed: 00:00:00.02
Execution Plan
----------------------------------------------------------
Plan hash value: 4243144607
------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 216 | 322 (1)| 00:00:04 |
| 1 | HASH GROUP BY | | 4 | 216 | 322 (1)| 00:00:04 |
|* 2 | HASH JOIN | | 8823 | 465K| 321 (1)| 00:00:04 |
| 3 | TABLE ACCESS FULL | DEPT | 4 | 52 | 3 (0)| 00:00:01 |
|* 4 | HASH JOIN | | 8823 | 353K| 318 (1)| 00:00:04 |
| 5 | MERGE JOIN CARTESIAN | | 3 | 57 | 6 (0)| 00:00:01 |
|* 6 | TABLE ACCESS FULL | JOBS | 1 | 11 | 3 (0)| 00:00:01 |
| 7 | BUFFER SORT | | 3 | 24 | 3 (0)| 00:00:01 |
|* 8 | TABLE ACCESS FULL | DATES | 3 | 24 | 3 (0)| 00:00:01 |
| 9 | VIEW | VW_ST_7A68B670 | 10587 | 227K| 311 (0)| 00:00:04 |
| 10 | NESTED LOOPS | | 10587 | 444K| 305 (0)| 00:00:04 |
| 11 | BITMAP CONVERSION TO ROWIDS| | 10586 | 186K| 6 (0)| 00:00:01 |
| 12 | BITMAP AND | | | | | |
| 13 | BITMAP MERGE | | | | | |
| 14 | BITMAP KEY ITERATION | | | | | |
|* 15 | TABLE ACCESS FULL | JOBS | 1 | 11 | 3 (0)| 00:00:01 |
|* 16 | BITMAP INDEX RANGE SCAN| EMP_FACT_JOB_BIX | | | | |
| 17 | BITMAP MERGE | | | | | |
| 18 | BITMAP KEY ITERATION | | | | | |
|* 19 | TABLE ACCESS FULL | DATES | 3 | 24 | 3 (0)| 00:00:01 |
|* 20 | BITMAP INDEX RANGE SCAN| EMP_FACT_DATE_BIX | | | | |
| 21 | TABLE ACCESS BY USER ROWID | EMP_FACT | 1 | 25 | 305 (1)| 00:00:04 |
------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("ITEM_1"="D"."DEPTNO")
4 - access("ITEM_3"="J"."JOB_ID" AND "ITEM_2"="D"."DATE_ID")
6 - filter("J"."JOB"='CLERK')
8 - filter("D"."YEAR"='1980' OR "D"."YEAR"='1987')
15 - filter("J"."JOB"='CLERK')
16 - access("E"."JOB_ID"="J"."JOB_ID")
19 - filter("D"."YEAR"='1980' OR "D"."YEAR"='1987')
20 - access("E"."DATE_ID"="D"."DATE_ID")
Note
-----
- star transformation used for this statement
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
1075 consistent gets
0 physical reads
0 redo size
824 bytes sent via SQL*Net to client
524 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
2 rows processed
今回は、star transformation used for this statement
の出力があるようにスター型変換されており、実行時間も 0.02秒と改善されている。
トランスポータブル表領域により DB 間で表領域の移動を行う。
マニュアルに記載の手順に従い、トランスポータブル表領域により DB 間で表領域の移動を行う。
tt_emp
表領域を作成する。
SQL> CREATE TABLESPACE tt_emp
DATAFILE '/u01/app/oracle/oradata/PROD1/tt_emp01.dbf'
SIZE 10M REUSE AUTOEXTEND ON;
Tablespace created.
SQL> CREATE TABLE scott.tt_emp TABLESPACE tt_emp AS SELECT * FROM scott.emp;
Table created.
異なるプラットフォーム間で表領域のトランスポートを行う場合は endianness が異なると変換が必要になる。 endianness を確認する。
SQL> set pages 100
col platform_name for a40
col endian_format for a15
SQL> SELECT * FROM V$TRANSPORTABLE_PLATFORM;
PLATFORM_ID PLATFORM_NAME ENDIAN_FORMAT
----------- ---------------------------------------- ---------------
1 Solaris[tm] OE (32-bit) Big
2 Solaris[tm] OE (64-bit) Big
7 Microsoft Windows IA (32-bit) Little
10 Linux IA (32-bit) Little
6 AIX-Based Systems (64-bit) Big
3 HP-UX (64-bit) Big
5 HP Tru64 UNIX Little
4 HP-UX IA (64-bit) Big
11 Linux IA (64-bit) Little
15 HP Open VMS Little
8 Microsoft Windows IA (64-bit) Little
9 IBM zSeries Based Linux Big
13 Linux x86 64-bit Little
16 Apple Mac OS Big
12 Microsoft Windows x86 64-bit Little
17 Solaris Operating System (x86) Little
18 IBM Power Based Linux Big
19 HP IA Open VMS Little
20 Solaris Operating System (x86-64) Little
21 Apple Mac OS (x86-64) Little
20 rows selected.
SQL> SELECT PLATFORM_NAME FROM V$DATABASE;
PLATFORM_NAME
----------------------------------------
Linux x86 64-bit
SQL> SELECT d.PLATFORM_NAME, ENDIAN_FORMAT
FROM V$TRANSPORTABLE_PLATFORM tp, V$DATABASE d
WHERE tp.PLATFORM_NAME = d.PLATFORM_NAME;
PLATFORM_NAME ENDIAN_FORMAT
---------------------------------------- ---------------
Linux x86 64-bit Little
表領域内に別の表領域の表の索引が含まれている場合などは自己完結型でないためトランスポートできない。 表領域が自己完結型かどうかチェックする。
SQL> EXECUTE DBMS_TTS.TRANSPORT_SET_CHECK('tt_emp', TRUE);
PL/SQL procedure successfully completed.
SQL> SELECT * FROM TRANSPORT_SET_VIOLATIONS;
no rows selected
もし、違反している場合は TRANSPORT_SET_VIOLATIONS
ビューに違反している内容が表示される。
表領域を読取り専用にする。
SQL> ALTER TABLESPACE tt_emp READ ONLY;
Tablespace altered.
expdp
によりエクスポートする。
$ expdp system/oracle dumpfile=expdat.dmp directory=data_pump_dir \
transport_tablespaces=tt_emp logfile=tts_export.log
Export: Release 11.2.0.3.0 - Production on Wed Sep 12 23:53:49 2012
Copyright (c) 1982, 2011, Oracle and/or its affiliates. All rights reserved.
Connected to: Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
FLASHBACK automatically enabled to preserve database integrity.
Starting "SYSTEM"."SYS_EXPORT_TRANSPORTABLE_01": system/******** dumpfile=expdat.dmp directory=data_pump_dir transport_tablespaces=tt_emp logfile=tts_export.log
Processing object type TRANSPORTABLE_EXPORT/PLUGTS_BLK
Processing object type TRANSPORTABLE_EXPORT/TABLE
Processing object type TRANSPORTABLE_EXPORT/POST_INSTANCE/PLUGTS_BLK
Master table "SYSTEM"."SYS_EXPORT_TRANSPORTABLE_01" successfully loaded/unloaded
******************************************************************************
Dump file set for SYSTEM.SYS_EXPORT_TRANSPORTABLE_01 is:
/u01/app/oracle/admin/PROD1/dpdump/expdat.dmp
******************************************************************************
Datafiles required for transportable tablespace TT_EMP:
/u01/app/oracle/oradata/PROD1/tt_emp01.dbf
Job "SYSTEM"."SYS_EXPORT_TRANSPORTABLE_01" successfully completed at 23:54:17
もし、endianness の異なるプラットフォームにトランスポートする場合は、RMAN でデータファイルの変換を行う。
$ export ORACLE_SID=PROD1
$ rman target /
RMAN> CONVERT TABLESPACE tt_emp
TO PLATFORM 'Apple Mac OS'
FORMAT '/tmp/%U';
Starting conversion at source at 12-09-12
using target database control file instead of recovery catalog
allocated channel: ORA_DISK_1
channel ORA_DISK_1: SID=43 device type=DISK
channel ORA_DISK_1: starting datafile conversion
input datafile file number=00006 name=/u01/app/oracle/oradata/PROD1/tt_emp01.dbf
converted datafile=/tmp/data_D-PROD1_I-2014160803_TS-TT_EMP_FNO-6_07nl25cg
channel ORA_DISK_1: datafile conversion complete, elapsed time: 00:00:01
Finished conversion at source at 12-09-12
今回は同一サーバ上の別 DB にした。
# エクスポート・ダンプ・ファイル をトランスポート先 DB の DATA_PUMP_DIR にコピー
$ cp -i /u01/app/oracle/admin/PROD1/dpdump/expdat.dmp /u01/app/oracle/admin/PROD2/dpdump/
# 通常はデータファイルをトランスポート先のデータファイルの位置にコピー
# 今回はテストとして endianness を変換したファイルをコピーした
$ cp -i /tmp/data_D-PROD1_I-2014160803_TS-TT_EMP_FNO-6_07nl25cg /tmp/tt_emp01.dbf
もし、endianness の異なるプラットフォームにトランスポートする場合でトランスポート元で変換を行っていない場合は、RMAN でデータファイルの変換を行う。
$ export ORACLE_SID=PROD2
$ rman target /
RMAN> CONVERT DATAFILE
'/tmp/tt_emp01.dbf'
TO PLATFORM 'Linux x86 64-bit'
FROM PLATFORM 'Apple Mac OS'
DB_FILE_NAME_CONVERT='/tmp/', '/u01/app/oracle/oradata/PROD2/';
Starting conversion at target at 12-09-13
using target database control file instead of recovery catalog
allocated channel: ORA_DISK_1
channel ORA_DISK_1: SID=36 device type=DISK
channel ORA_DISK_1: starting datafile conversion
input file name=/tmp/tt_emp01.dbf
converted datafile=/u01/app/oracle/oradata/PROD2/tt_emp01.dbf
channel ORA_DISK_1: datafile conversion complete, elapsed time: 00:00:01
Finished conversion at target at 12-09-13
ALTER TABLESPACE
にて戻す。
$ export ORACLE_SID=PROD1
$ sqlplus '/as sysdba'
SQL> ALTER TABLESPACE tt_emp READ WRITE;
Tablespace altered.
impdp
によりインポートする。
$ export ORACLE_SID=PROD2
$ impdp system/oracle dumpfile=expdat.dmp directory=data_pump_dir \
transport_datafiles='/u01/app/oracle/oradata/PROD2/tt_emp01.dbf' \
logfile=tts_import.log
Import: Release 11.2.0.3.0 - Production on Thu Sep 13 00:22:31 2012
Copyright (c) 1982, 2011, Oracle and/or its affiliates. All rights reserved.
Connected to: Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
Master table "SYSTEM"."SYS_IMPORT_TRANSPORTABLE_01" successfully loaded/unloaded
Starting "SYSTEM"."SYS_IMPORT_TRANSPORTABLE_01": system/******** dumpfile=expdat.dmp directory=data_pump_dir transport_datafiles=/u01/app/oracle/oradata/PROD2/tt_emp01.dbf logfile=tts_import.log
Processing object type TRANSPORTABLE_EXPORT/PLUGTS_BLK
Processing object type TRANSPORTABLE_EXPORT/TABLE
Processing object type TRANSPORTABLE_EXPORT/POST_INSTANCE/PLUGTS_BLK
Job "SYSTEM"."SYS_IMPORT_TRANSPORTABLE_01" successfully completed at 00:22:34
ALTER TABLESPACE
にて戻す。
$ export ORACLE_SID=PROD2
$ sqlplus '/as sysdba'
SQL> ALTER TABLESPACE tt_emp READ WRITE;
Tablespace altered.
]]>集計(AVG(sal)
など)や結合(emp.deptno = dept.deptno
など)を含むマテリアライズド・ビューを高速リフレッシュ可能にする。
集計(AVG(sal)
など)を含むマテリアライズド・ビューを高速リフレッシュ可能にする。
マニュアルから、集計関数(SUM
, COUNT(*)
, AVG
など)を含むマテリアライズド・ビューを高速リフレッシュ可能にするためにはいくつか制限がある。
ざっくり含める必要がある要件をまとめると以下。
マテリアライズド・ビュー・ログの要件
ROWID
および INCLUDING NEW VALUES
句を指定SEQUENCE
句を指定マテリアライズド・ビューの SELECT 文に含める要件
COUNT(*)
を含めるAVG(expr)
などの集計ごとに、対応する COUNT(expr)
および SUM(expr)
を含める。(集計関数によって要件は異なる)例えば以下の SELECT を含むマテリアライズド・ビューを考える。
SELECT d.dname, AVG(e.sal) avg_sal
FROM emp e, dept d
WHERE e.deptno = d.deptno
GROUP BY d.dname;
高速リフレッシュ可能なマテリアライズド・ビューを作成する。
CREATE MATERIALIZED VIEW LOG
文を実行
SQL> CREATE MATERIALIZED VIEW LOG ON emp
WITH SEQUENCE, ROWID
(sal, deptno)
INCLUDING NEW VALUES;
Materialized view log created.
SQL> CREATE MATERIALIZED VIEW LOG ON dept
WITH SEQUENCE, ROWID
(deptno, dname)
INCLUDING NEW VALUES;
Materialized view log created.
CREATE MATERIALIZED VIEW
文を実行
SQL> CREATE MATERIALIZED VIEW emp_dept_mv
BUILD IMMEDIATE
REFRESH FAST
ENABLE QUERY REWRITE
AS SELECT d.dname, AVG(e.sal) avg_sal,
COUNT(*) cnt, COUNT(e.sal) cnt_sal, SUM(e.sal) sum_sal
FROM emp e, dept d
WHERE e.deptno = d.deptno
GROUP BY d.dname;
Materialized view created.
DBMS_MVIEW.EXPLAIN_MVIEW
プロシージャを使用することで簡単にマテリアライズド・ビューに関して次のことを確認できる。
使い方は以下
$ORACLE_HOME/rdbms/admin/utlxmv.sql
を流して MV_CAPABILITIES_TABLE
表を作成DBMS_MVIEW.EXPLAIN_MVIEW
プロシージャの引数にマテリアライズド・ビュー名かマテリアライズド・ビューで使用している SELECT 文を与えて実行MV_CAPABILITIES_TABLE
表を SELECT して確認DBMS_MVIEW.EXPLAIN_MVIEW
プロシージャを実行する場合は TRUNCATE TABLE MV_CAPABILITIES_TABLE
で結果をクリア先ほど作成したマテリアライズド・ビューで確認してみる。
SQL> @?/rdbms/admin/utlxmv.sql
Table created.
SQL> EXEC DBMS_MVIEW.EXPLAIN_MVIEW('emp_dept_mv');
PL/SQL procedure successfully completed.
SQL> set lines 200
set pages 100
col capability_name for a30
col rel_text for a10
col msgtxt for a60
SQL> SELECT capability_name, possible, SUBSTR(related_text,1,8)
AS rel_text, SUBSTR(msgtxt,1,60) AS msgtxt
FROM MV_CAPABILITIES_TABLE
ORDER BY seq;
CAPABILITY_NAME POS REL_TEXT MSGTXT
------------------------------ --- ---------- ------------------------------------------------------------
PCT N
REFRESH_COMPLETE Y
REFRESH_FAST Y
REWRITE Y
PCT_TABLE N EMP relation is not a partitioned table
PCT_TABLE N DEPT relation is not a partitioned table
REFRESH_FAST_AFTER_INSERT Y
REFRESH_FAST_AFTER_ONETAB_DML Y
REFRESH_FAST_AFTER_ANY_DML Y
REFRESH_FAST_PCT N PCT is not possible on any of the detail tables in the mater
REWRITE_FULL_TEXT_MATCH Y
REWRITE_PARTIAL_TEXT_MATCH Y
REWRITE_GENERAL Y
REWRITE_PCT N general rewrite is not possible or PCT is not possible on an
PCT_TABLE_REWRITE N EMP relation is not a partitioned table
PCT_TABLE_REWRITE N DEPT relation is not a partitioned table
16 rows selected.
possible
列が Y
であることから高速リフレッシュ可能であることが分かる。(PCT*
に関してはパーティション表ではないので無視)
例えば、以下のようにマテリアライズド・ビュー作成前に高速リフレッシュ可能か確認でき、可能でない場合は理由を表示してくれる。
SQL> TRUNCATE TABLE MV_CAPABILITIES_TABLE;
Table truncated.
SQL> -- わざと COUNT(e.sal), SUM(e.sal) を外してみる
SQL> BEGIN
DBMS_MVIEW.EXPLAIN_MVIEW('
SELECT d.dname, AVG(e.sal) avg_sal,
COUNT(*) cnt
FROM emp e, dept d
WHERE e.deptno = d.deptno
GROUP BY d.dname');
END;
/
PL/SQL procedure successfully completed.
SQL> SELECT capability_name, possible, SUBSTR(related_text,1,8)
AS rel_text, SUBSTR(msgtxt,1,60) AS msgtxt
FROM MV_CAPABILITIES_TABLE
ORDER BY seq;
CAPABILITY_NAME POS REL_TEXT MSGTXT
------------------------------ --- ---------- ------------------------------------------------------------
PCT N
REFRESH_COMPLETE Y
REFRESH_FAST N
REWRITE Y
PCT_TABLE N EMP relation is not a partitioned table
PCT_TABLE N DEPT relation is not a partitioned table
REFRESH_FAST_AFTER_INSERT N AVG_SAL agg(expr) requires correspondng COUNT(expr) function
REFRESH_FAST_AFTER_ONETAB_DML N see the reason why REFRESH_FAST_AFTER_INSERT is disabled
REFRESH_FAST_AFTER_ANY_DML N see the reason why REFRESH_FAST_AFTER_ONETAB_DML is disabled
REFRESH_FAST_PCT N PCT is not possible on any of the detail tables in the mater
REWRITE_FULL_TEXT_MATCH Y
REWRITE_PARTIAL_TEXT_MATCH Y
REWRITE_GENERAL Y
REWRITE_PCT N general rewrite is not possible or PCT is not possible on an
PCT_TABLE_REWRITE N EMP relation is not a partitioned table
PCT_TABLE_REWRITE N DEPT relation is not a partitioned table
16 rows selected.
高速リフレッシュが不可で、AVG_SAL
列に関して対応する COUNT(expr)
が必要なことを指摘してくれる。
集計関数は含まずに結合(emp.deptno = dept.deptno
など)のみを含むマテリアライズド・ビューを高速リフレッシュ可能にする。
マニュアルから、結合のみを含むマテリアライズド・ビューを高速リフレッシュ可能にするためにはいくつか制限がある。 ざっくり含める必要がある要件をまとめると以下。
マテリアライズド・ビュー・ログの要件
ROWID
句を指定マテリアライズド・ビューの SELECT 文に含める要件
FROM
内の全ての表の ROWID を含める例えば以下の SELECT を含むマテリアライズド・ビューを考える。
SELECT e.ename, d.loc
FROM emp e, dept d
WHERE e.deptno = d.deptno;
高速リフレッシュ可能なマテリアライズド・ビューを作成する。
CREATE MATERIALIZED VIEW LOG
文を実行
SQL> CREATE MATERIALIZED VIEW LOG ON emp
WITH ROWID;
Materialized view log created.
SQL> CREATE MATERIALIZED VIEW LOG ON dept
WITH ROWID;
Materialized view log created.
CREATE MATERIALIZED VIEW
文を実行
SQL> CREATE MATERIALIZED VIEW emp_dept_mv
BUILD IMMEDIATE
REFRESH FAST
ENABLE QUERY REWRITE
AS SELECT e.ename, d.loc,
e.rowid e_rowid, d.rowid d_rowid
FROM emp e, dept d
WHERE e.deptno = d.deptno;
Materialized view created.
先ほど行ったように DBMS_MVIEW.EXPLAIN_MVIEW
プロシージャで確認する。
SQL> TRUNCATE TABLE MV_CAPABILITIES_TABLE;
Table truncated.
SQL> EXEC DBMS_MVIEW.EXPLAIN_MVIEW('emp_dept_mv');
PL/SQL procedure successfully completed.
SQL> SELECT capability_name, possible, SUBSTR(related_text,1,8)
AS rel_text, SUBSTR(msgtxt,1,60) AS msgtxt
FROM MV_CAPABILITIES_TABLE
ORDER BY seq;
CAPABILITY_NAME POS REL_TEXT MSGTXT
------------------------------ --- ---------- ------------------------------------------------------------
PCT N
REFRESH_COMPLETE Y
REFRESH_FAST Y
REWRITE Y
PCT_TABLE N EMP relation is not a partitioned table
PCT_TABLE N DEPT relation is not a partitioned table
REFRESH_FAST_AFTER_INSERT Y
REFRESH_FAST_AFTER_ONETAB_DML Y
REFRESH_FAST_AFTER_ANY_DML Y
REFRESH_FAST_PCT N PCT is not possible on any of the detail tables in the mater
REWRITE_FULL_TEXT_MATCH Y
REWRITE_PARTIAL_TEXT_MATCH Y
REWRITE_GENERAL Y
REWRITE_PCT N general rewrite is not possible or PCT is not possible on an
PCT_TABLE_REWRITE N EMP relation is not a partitioned table
PCT_TABLE_REWRITE N DEPT relation is not a partitioned table
16 rows selected.
PCT*
以外は possible
列が Y
になっているので高速リフレッシュ可能。
Data Guard のファスト・スタート・フェイルオーバーにより、プライマリ DB での障害発生時に自動でフェイルオーバーするようにする。
Data Guard のファスト・スタート・フェイルオーバーにより、プライマリ DB での障害発生時に自動でフェイルオーバーするようにできる。 ファスト・スタート・フェイルオーバーには、プライマリ DB、スタンバイ DB を監視する”オブザーバ”が必要。オブザーバはプライマリ DB、スタンバイ DB とは別のサーバに配置することが望ましいが、今回はスタンバイ DB と同じサーバに構成する。
EM Grid Control を使用してファスト・スタート・フェイルオーバーを有効にする。
なぜか上記画面のまま “概要” のページに戻らない。alert.log を確認して処理が終わっていそうだったら、EM の画面を切り替えてしまってよい。
stdby1.local
はスタンバイ DB, sv2.local
はオブザーバを構成したサーバで今回はスタンバイ DB と同じサーバ)ファスト・スタート・フェイルオーバーが動作するか検証してみる。 マニュアル Data Guard Broker によると次の場合にファスト・スタート・フェイルオーバーが試行される。
正常にデータベースを停止した場合(NORMAL, IMMEDIATE, TRANSACTIONAL)では、ファスト・スタート・フェイルオーバーは試行されないので、プライマリ DB を停止するときは通常通り ABORT 以外のオプションで SHUTDOWN すればいい。
ファスト・スタート・フェイルオーバーを検証するためにプライマリ DB を shutdown abort
で強制終了する。
# プライマリ側で実施
$ export ORACLE_SID=PROD1
$ sqlplus '/as sysdba'
SQL> shutdown abort
ORACLE instance shut down.
これで、プライマリ DB の異常をオブザーバが検知して、フェイルオーバーが自動で開始される。
オブザーバは Data Guard Broker の dgmgrl コマンドで実行されているので、ログは $ORACLE_HOME/rdbms/log/dgmgrl_XXXX_XXXX.log
に出力されている。
# オブザーバ配置サーバで実施
$ ps -ef | grep dgmgrl
oracle 12805 1 0 20:27 ? 00:00:01 /u01/app/oracle/product/11.2.0/dbhome_1/bin/dgmgrl -logfile /u01/app/oracle/product/11.2.0/dbhome_1/rdbms/log/dgmgrl_PROD1_12804.log
$ cat $ORACLE_HOME/rdbms/log/dgmgrl_PROD1_12804.log
Observer started
[W000 09/11 20:27:30.89] Observer started.
21:07:33.37 2012年9月11日 Tuesday
Initiating Fast-Start Failover to database "stdby1"...
Performing failover NOW, please wait...
Failover succeeded, new primary is "stdby1"
21:07:35.66 2012年9月11日 Tuesday
これで、フェイルオーバーが実施された。EM の元スタンバイ DB 側を確認すると以下のようになっている。
この後、元プライマリ DB を起動すると、自動でスタンバイ DB として構成してくれる。
# 元プライマリ側で実施
$ export ORACLE_SID=PROD1
$ sqlplus '/as sysdba'
SQL> shutdown abort
ORACLE instance shut down.
SQL> startup
ORACLE instance started.
Total System Global Area 835104768 bytes
Fixed Size 2232960 bytes
Variable Size 511708544 bytes
Database Buffers 314572800 bytes
Redo Buffers 6590464 bytes
Database mounted.
ORA-16649: possible failover to another database prevents this database from
being opened
オブザーバのログを確認すると、元プライマリ DB が新たにスタンバイ DB としてマウントされたログ出力がある。
# オブザーバ配置サーバで実施
$ cat $ORACLE_HOME/rdbms/log/dgmgrl_PROD1_12804.log
21:18:47.02 2012年9月11日 Tuesday
Initiating reinstatement for database "PROD1"...
Reinstating database "PROD1", please wait...
Operation requires shutdown of instance "PROD1" on database "PROD1"
Shutting down instance "PROD1"...
ORA-01109: database not open
Database dismounted.
ORACLE instance shut down.
Operation requires startup of instance "PROD1" on database "PROD1"
Starting instance "PROD1"...
ORACLE instance started.
Database mounted.
Continuing to reinstate database "PROD1" ...
Reinstatement of database "PROD1" succeeded
21:19:33.19 2012年9月11日 Tuesday
EM の元スタンバイ DB 側を確認すると以下のように元プライマリ DB が新たにスタンバイ DB として構成されていることが分かる。
この後、スイッチオーバすれば、元の構成に戻る。
マニュアル に記載があるように、ファスト・スタート・フェイルオーバー環境でのデータベースの停止する場合は次のようにする。
オブザーバを停止し、プライマリ DB およびターゲット・スタンバイ DB の両方について、V$DATABASE.FS_FAILOVER_OBSERVER_PRESENT 列が NO
になるまで待機する。こうすると、プライマリ DB の停止中に、ファスト・スタート・フェイルオーバーは実行されない。
プライマリ DB およびターゲット・スタンバイ DB を停止(SHUTDOWN
)する。
以下が実行例。
dgmgrl
にてオブザーバを停止
$ export ORACLE_SID=stdby1
$ dgmgrl
DGMGRL for Linux: Version 11.2.0.3.0 - 64bit Production
Copyright (c) 2000, 2009, Oracle. All rights reserved.
Welcome to DGMGRL, type "help" for information.
DGMGRL> connect sys/oracle
Connected.
DGMGRL> stop observer;
Done.
DGMGRL> exit
プライマリ DB およびスタンバイ DB にてV$DATABASE.FS_FAILOVER_OBSERVER_PRESENT 列が NO
になることを確認して SHUTDOWN
する。
# プライマリ DB および スタンバイ DB でそれぞれ実施
$ sqlplus 'as sysdba'
SQL> SELECT FS_FAILOVER_OBSERVER_PRESENT FROM V$DATABASE;
FS_FAILOVER_OBSERVER_
---------------------
YES
SQL> /
FS_FAILOVER_OBSERVER_
---------------------
NO
SQL> shutdown immediate
Database closed.
Database dismounted.
ORACLE instance shut down.
SQL> exit
起動時はプライマリ DB およびスタンバイ DB を起動した後に、dgmgrl
でオブザーバを起動(start observer
)する。