技術の犬小屋

Webプログラミングを餌に生きる犬のメモ帳

RDBのパフォーマンスを改善する手段として、「テーブル分割」という方法がある。更に、テーブル分割に代替する手段である「集約」についても本記事で取り上げる。
 

テーブル分割は行うべきではない

テーブル分割は原則として行うべきではない。理由は、テーブル分割はそれが行われる意味的な理由を持たず、テーブルを分割する理由が正規化の理論からは出てこないからだ。テーブル分割はパフォーマンス要求のみによって行われ、テーブルに意味的な破壊をもたらすことに注意しなければならない。そして、テーブル分割には代替手段が存在する。テーブル分割を行わずとも「パーティション」や「集約」といった方法で、テーブル分割に相当する効果が得られるので、そちらを利用するべきである。
 

テーブル分割の種類

テーブル分割を分類すると、大きく以下の2つに分かれる。

  • 水平分割
  • 垂直分割

 
また、厳密には分割ではないが「集約」という、テーブル分割の代替手段的な方法も存在するので、これもあわせて本記事で解説する。
 
「水平分割」は、レコード単位にテーブルを分割することである。テーブルを水平にカットするのでこう呼ばれる。「垂直分割」は、その反対で、列単位にテーブルを分割する。
 
水平分割のイメージ
水平分割
 
垂直分割のイメージ
垂直分割
 
以下より、「水平分割」「垂直分割」について、それぞれ解説する。
 

水平分割

水平分割とは、レコード単位でテーブルを分割する手段である。
 
例として、会社の1年ごとの売上げを保持する以下のようなテーブルを考えてみる。
 

売上げ
年度 会社コード 売上げ(億円)
2001 C0001 50
2001 C0002 52
2001 C0003 55
2001 C0004 46
2002 C0001 52
2002 C0002 55
2002 C0003 60
2002 C0004 47
2003 C0001 46
2003 C0002 52
2003 C0003 44
2003 C0004 60

 
このテーブルは、サンプルなので12行しかレコードを含んでいないが、実際の業務システムではテーブルに何百万~何十億という数のレコードが含まれる。その結果、テーブルにアクセスするSQLのパフォーマンスが悪化することが起こることがある。
そこで、パフォーマンス改善について考えられる手段の一つが、SQL文がアクセスするテーブルのサイズを極力小さくしようとすることである。例えば、SQLが常に1年ごとにしか「売上げ」テーブルにアクセスしないのであれば、次のように年度ごとにテーブルを分割することでパフォーマンスを改善することができる。
 

売上げ(2001)
年度 会社コード 売上げ(億円)
2001 C0001 50
2001 C0002 52
2001 C0003 55
2001 C0004 46

 

売上げ(2002)
年度 会社コード 売上げ(億円)
2002 C0001 52
2002 C0002 55
2002 C0003 60
2002 C0004 47

 

売上げ(2003)
年度 会社コード 売上げ(億円)
2003 C0001 46
2003 C0002 52
2003 C0003 44
2003 C0004 60

 
これは分かりやすいと言えば分かりやすい解決策だが、冒頭で説明した通り、RDBでは原則禁止とされている。この方法に最も近い代替策としてパーティションの利用が挙げられる。このパーティション機能は、DBMSによっては持っていなかったり、有料オプションだったりするが利用可能な環境においては、水平分割を回避しつつパフォーマンス改善が可能になる為、積極的に利用するべきである。
 

垂直分割

水平分割がレコードを軸にした分割だったのに対して、垂直分割は列を軸に分割する。
 
例として、以下のような第3正規化された状態の「社員」テーブルを考えてみる。
 

社員
会社コード 社員ID 社員名 年齢 部署コード
C0001 000A 加藤 40 D01
C0001 000B 藤本 32 D02
C0001 001F 三島 50 D03
C0002 000A 斉藤 47 D03
C0002 009F 田島 25 D01
C0002 010A 渋谷 33 D04

 
今、このテーブルに対する検索のSQL文に遅延が発生していて、改善の必要があるとする。かつ、その検索で利用する列は、常に「会社コード」「社員ID」および「年齢」だけであるとする。このとき、次のようにテーブルを分割することでSQL文がアクセスするデータ量を減らすことができる。
 

社員1
会社コード 社員ID 年齢
C0001 000A 40
C0001 000B 32
C0001 001F 50
C0002 000A 47
C0002 009F 25
C0002 010A 33

 

社員2
会社コード 社員ID 社員名 部署コード
C0001 000A 加藤 D01
C0001 000B 藤本 D02
C0001 001F 三島 D03
C0002 000A 斉藤 D03
C0002 009F 田島 D01
C0002 010A 渋谷 D04

 
ボトルネックがストレージのI/Oコストだった場合に限るが、このように必要な列だけに絞ってデータを保持した「社員1」テーブルを検索対象とすることで、SQL文のパフォーマンス改善が可能になる。
しかし、この垂直分割にも、分割することが論理的な意味を持たないという水平分割と同様の欠点があるため、原則利用するべきではない。特に垂直分割の場合は次に紹介する「集約」で代替可能である。
 

集約

集約は、テーブル分割ではなく、むしろテーブル分割の代替案に位置付けらえる方法である。その種類は、さらに細かく以下の2種類に分けられる。

  • 列の絞り込み
  • サマリテーブル

 

列の絞り込み

まず1つ目が、単純に保持するテーブルを作成するタイプである。これは、先ほどの垂直分割に対する代替案に相当する。「社員」テーブルのうち「会社コード」「社員ID」および「年齢」が頻繁に参照される列であるならば、これらの列だけを持った新しいテーブルを追加作成するわけである。オリジナルの「社員」テーブルは残す為、分割ではない。
 
例として、以下の「社員」テーブルに対して列の絞り込みを行う場合を考えてみる。
 

社員
会社コード 社員ID 社員名 年齢 部署コード
C0001 000A 加藤 40 D01
C0001 000B 藤本 32 D02
C0001 001F 三島 50 D03
C0002 000A 斉藤 47 D03
C0002 009F 田島 25 D01
C0002 010A 渋谷 33 D04

 
列の絞り込みを行って作成したテーブルは以下である。この2つのテーブルは定期的にデータの同期が必要となる。
 

社員(年齢のみ)
会社コード 社員ID 年齢
C0001 000A 40
C0001 000B 32
C0001 001F 50
C0002 000A 47
C0002 009F 25
C0002 010A 33

 
このようにして作られる、オリジナルと比較して小規模なテーブルのことを、データマート(Data Mart)、あるいは省略して単にマートと呼ぶ。
マートは非常に便利で、オリジナルのテーブルを意味的に破壊することなくパフォーマンスを向上させることができる。また、マートを利用する際に注意すべき問題があり、それがデータ同期の問題である。
今、オリジナルのテーブルとマートは、部分的に同じ列を共有している。このとき、たとえば「年齢」列は社員が年齢を重ねる度に値を更新していく必要がある。そのため、オリジナルの「社員」テーブルの「年齢」列が更新されたら、マートの「年齢」列も更新しなければならない。
問題となるのは、マートの更新タイミングである。このタイミングが短いほど、オリジナルのテーブルと齟齬がある期間も短くなるので、データ精度が高く、機能的に好ましいが、更新タイミングが短いほど更新処理の負荷が上がり、もともと解決するはずだった性能問題をかえって悪化させてしまう危険もある。したがって、多くの場合、マートの更新は1日1回~数回程度の頻度で一括更新(バッチ更新)されている。
 

サマリテーブル

サマリテーブルも集約の一手段である。列の絞り込みと違うのは、サマリテーブルは集約関数(SUM,AVGなど)によってレコードを集約した状態で保持することである。たとえば、会社別に社員の平均年齢が必要な業務があったとする。このとき、もちろん毎回「社員」テーブルに対してSELECT文でアクセスして集約関数(AVG)を実行しても良いが、テーブルの規模が大きくなると、その集約関数による処理のコストが大きくなり、実行時間が長くなる。
そこで、事前に集約を行ったテーブルを作っておくことで、社員の会社別平均年齢を求めたければ、そのテーブルに対する単純なSELECT文で求めることができる。
 
例として、以下の「社員」テーブルに対してサマリテーブルの作成を行う場合を考えてみる。
 

社員
会社コード 社員ID 社員名 年齢 部署コード
C0001 000A 加藤 40 D01
C0001 000B 藤本 32 D02
C0001 001F 三島 50 D03
C0002 000A 斉藤 47 D03
C0002 009F 田島 25 D01
C0002 010A 渋谷 33 D04

 
「社員」テーブルを対象に、会社別に社員の平均年齢を求める為にサマリテーブルを作成する。
 
以下がサマリテーブルである。
 

社員平均年齢
会社コード 平均年齢
C0001 41
C0002 35

 
この「社員平均年齢」テーブルのサイズは、行列ともに元の「社員」テーブルより小さくなり、アクセスするときのI/Oコストを大きく削減する効果がある。ただし、この方法も先ほどの列の絞り込みと同じデメリットを共有している。つまり、更新のタイムラグによってデータの整合性がとれない時間帯が存在するということである。
 

まとめ

  • テーブル分割は行うべきではない
  • テーブルの水平分割は、パーティションで代替することができる
  • テーブルの垂直分割は、集約で代替することができる

 
 
以上
 
 
参考
書籍 達人に学ぶDB設計 徹底指南書 初級者で終わりたくないあなたへ

Javaのプリミティブ型と参照型について arrow-right
Next post

arrow-left HTMLのテーブルの正しい書き方について
Previous post

コメントを残す