表定義でのカラムの並び順によるチューニング
カラム数が多い表の場合でも、表定義のカラムの順番を見かけ上の軽微な問題として物理設計を行なっていることが少なくない。
一般的な OLTP ではカラム数も少なくレコード数も百万件程度なので数秒〜最悪 10数秒程度かもしれない。
この問題は、表を別定義して ダイレクト・パス・インサートで移行、リネーム処理すれば容易に解消できるので VLDB の場合には、今からでも良く考慮した方がよい。(データを格納できるデータセグメントと一時セグメント領域に空き領域が十分があることが前提)
オンライン表再定義パッケージ(DBMS_REDEFINITION.〜) によってオンライン中でも再定義が可能
1 レコードのレコード長
全く同じデータであってもカラムの定義順によりレコード長が異なる。
これはレコードの末尾の連続した NULL は 列ヘッダも完全に TRIM されることによるものである。
レコードの各カラムには列ヘッダと呼ばれる制御データと列データ(実際に格納されるデータ)で構成される。
(列ヘッダには、列データのバイトサイズを管理する情報格納されている)
|列ヘッダ1|実データ1|列ヘッダ2|実データ2|...
しかし、レコードの末尾から NULL 値が連続しているレコードの場合には、末尾の連続した NULL 値は削除される。
これには列ヘッダも含まれる。
|列ヘッダ1|実データ1| レコードデータ終了
途中 NULL が連続していても 列の最後が NULLでない場合には
|列ヘッダ1|実データ1|列ヘッダ2|列ヘッダ3|......|列ヘッダ|列データ|
となり列ヘッダはすべてのカラムに存在する。
この特性を利用すると NULL 値が多いカラムは表定義の後方に配置すると列ヘッダ分の大きさ分レコード長を圧縮することができる。
行移行の発生頻度
上記の NULL が多いカラムは表定義の後方に配置することによりレコード長を圧縮した場合、
最終カラムを設定するとすべての列ヘッダが必要になる。例えば、論理削除フラグやレコード更新日付のカラムが典型である。
実際のレコード長はデータの更新によるデータの増加分に加えて省略されていた列ヘッダ分も増加する。
その結果、PCTFREE の値によっては想定外の 行移行 が発生するというデメリットがある。
定義並び順と DML の応答時間の関連性
定義の並び順と DML のレスポンスには多少関連性がある。
簡単に表現すると
「レコードは配列ではなくリストによって管理されている」
と言い換えれば、ピンとくるかと思います。
但し、オラクルデータベースの構造とバッファキャッシュ内での構造が同一であるという仮定における推測。
バッファ中においてリスト構造を変換していることも否定はできないが、現在の演算処理能力ではトータルレスポンスを低下させる可能性を想像することができる。
もし、より効率的になるならば、オラクルデータベースの 行断片構造 を変更してくると思われる。
配列であれば n 番目の要素にアクセスするのに添え字でダイレクトに操作できるがリスト管理ではそうはいかない。
列ヘッダを見ながら、レコードデータの不要な箇所をスキップしている。
すなわち、レコードの末尾カラムへアクセスするためのコストは、レコード中で最も高くなる(=遅い)
但し、インデックスを作成したカラムの場合には並び順に関係なく検索時に限ってコストが低い可能性は非常に高い。
(検証) n1〜n1000 までの数値型のカラムをもつテーブルで 20万レコードのデータをもつテーブルの集計を行った。
(データ長に不公平がでないように、数値はすべて実サイズが 4 バイトの数値とする)
サーバーの CPU 性能が低いと応答時間差がより顕著になる。(以下のテストは P4-HT 3.2GHz Windows XP の場合)
エンタープライズ向けの高性能 UNIX サーバーの場合 1000回メモリアクセス/レコード レベルでは、(レコード単位に 簡単なループ処理 1000回と考えれば) 百万件程度でも変わらないと予想できる。
しかし、作業負荷がピーク時(CPU が既に高負荷の状態)のサーバーにおいては、そこで並行して発行しているクエリーの邪魔をしているのではないか? という用心深さもチューニング作業には必要であると思う。
カラム数が数十程度では単独実行において、差異の検出は難しい。(誤差の範囲になってしまう)
(この実験内容がお膳立てられた最悪な状態である事を忘れないでください。)
SQL> alter system flush buffer_cache ;
経過: 00:00:00.01
SQL> select sum(n2) from tbl_n1000;
SUM(N2)
----------
2.0000E+10
経過: 00:00:42.50
...しばらく 安静 ( HDD アクセスランプを確認 )
SQL> alter system flush buffer_cache ;
経過: 00:00:00.01
SQL> select sum(n1000) from tbl_n1000;
SUM(N1000)
----------
2.0200E+10
経過: 00:00:47.23
(検証での注意) alter system flush buffer_cache は他のユーザーに重大な影響を及ぼすコマンドなので共有 DBサーバー等で安易に実行してはいない。
⇒ DBA Tips(分析・チューニング)
カラムの定義位置の選定
- レコードにおける良く参照する重要項目は定義の初めに配置する。
- 密接に関連するカラムは隣同士、または、その近辺に配置する。(ブロック内連鎖 の位置に注意)
- レコード数が多く、インデックスを併設しない(※)重要項目を追加定義する場合
(定期メンテナンス時に)可能な範囲で表を再定義を行って追加カラムを適切な位置にして再作成を行なう。
- データライフサイクルの最初から最終段階まで NULL 値であるの確率の高い項目は最後にまとめて配置する。
- 列ヘッダはデータ長により 1 バイト(250 バイト以下)または 3 バイト(251 バイト以上)の 二種類あるので覚えておく。
(※) インデックスを作成したからといって常にインデックスを使用するとは限らない。
補足
カラム定義の位置は注意深く選定する必要はありますが、過度に神経質になって再定義しても限られたメンテナンス時間を無駄遣いすることになります。
通常の OLTP の処理件数で、かつ、カラム数が 255 以下の(ブロック内連鎖がない)場合では、その応答時間の差は非常に小さいはずです。(検証はしていませんが)
それに、通常はデータモデラの経験則によって見やすく、理解度を増すためにエンティティの重要項目は上部に配置され、関連項目はまとめられ、上記のルールは守られています。
そのため、そのまま物理設計すれば良いでしょう。