社内勉強会:ソーシャルゲームのデータベース設計入門
DESCRIPTION
TRANSCRIPT
社内勉強会
データベース設計入門
山田直行(株式会社イストピカ)2011年3月4日
2011年3月5日土曜日
アジェンダ
• リレーショナルDB設計の基本正規化/行指向設計・列指向設計/実例を見ていく
• IndexIndexの基本/アンチパターン/Covering Index/Primary Keyの決め方/ReadとWriteのバランス/短期と長期の負荷対策
• MySQLのレプリケーションレプリケーションとは/スレーブDBが遅延するわけ/やってはいけないSQL
2011年3月5日土曜日
正規化
• 第一正規化カラムが単一のデータを持つようにすること→(10001,A)みたいにリスト形式でデータを持ったりしてはいけない※シリアライズした場合など、例外あり
• 第二正規化レコードのそれぞれの値がPrimary Keyによって一意に決まること→Primary Keyの一部ではダメ
• 第三正規化レコードのそれぞれの値が(Primary Keyを含む)他のカラムに依存していないこと
2011年3月5日土曜日
正規化の例(1)
• 第一正規形
ユーザーID ブランド
1 シスモード
2 シスモード
2 クスココ
ユーザーID ブランド
1 シスモード
2 シスモード、クスココ
2011年3月5日土曜日
正規化の例(2)
• 第二正規形
ユーザーID アイテムID アイテム名
1 10001 やくそう
1 10002 ポーション
2 10001 やくそう
2 10002 ポーション
ユーザーID アイテムID
1 10001
1 10002
2 10001
2 10002
アイテムID アイテム名
10001 やくそう
10002 ポーション
2011年3月5日土曜日
正規化の例(3)
• 第三正規形
ユーザーID 土地ID 土地名
1 101 新宿
2 102 渋谷
3 103 池袋
4 102 渋谷
ユーザーID 土地ID
1 101
2 102
3 103
4 102
土地ID アイテム名101 新宿102 渋谷103 池袋
2011年3月5日土曜日
行指向・列指向 ALTER TABLEはコストが高い
• 行(レコード)はいつでも追加していける
• 列(カラム)を追加するにはテーブル構造の変更(ALTER TABLE)が必要→ALTER TABLEは一時的なテーブル全コピーを伴うため、メンテ必須
• 列指向で設計して、使わない可能性があるカラムが多くあると、ディスクサイズ肥大化の原因になる
• ただし、列指向のほうがデータとしては直感的で人間が管理しやすく、列指向も場合によっては開発上、有効なケースもあるかもしれない→極限まで負荷がこないケースでは、大して差異がないのが現状
2011年3月5日土曜日
どちらがいいのか?
• 両者を比較してみないと一概にはいえないが、仕様変更に耐えうる柔軟な設計なのは行指向。レコード数が少なく、直感的で管理しやすいのは列指向
ユーザーID 目 鼻 口 髪 トップス
ボトムス
靴 アクセ1 101 234 553 232 873 0 102 0
2 102 271 8652 98 0 234 202 0
3 103 952 76 765 872 45 0 0
4 102 197 2312 762 4 232 672 34
パーツ種別 パーツ名1 目2 鼻3 口4 髪
ユーザーID パーツ種別 パーツID
1 1 101
1 2 234
1 3 553
1 4 232
2 1 102
2 2 271
2 3 952
行指向
列指向
2011年3月5日土曜日
Indexの基本
• WHERE句で検索する順にIndexを貼る(またはそれをPrimary Keyにする)
WHERE user_id = 10001
→ user_idにIndex(またはPrimary Key)を貼る
WHERE user_id = 10001 AND friend_id = 10002
→ (user_id, friend_id)でマルチカラムインデックスを貼る
WHERE status = 2 ORDER BY start_time
→ (status,start_time)でマルチカラムインデックスを貼る
WHERE visiting_id = 10001 AND visited_id IN (1,2,3) AND action=2
→ (visiting_id,visited_id,action)でマルチカラムインデックスを貼る
2011年3月5日土曜日
Indexのアンチパターン
• 範囲検索ではIndexは使われない
WHERE start_time < 12345678 AND state = 2
→(start_time,state)というインデックスは意味をなさない。(state,start_time)なら意味あり
WHERE visited_id = 10002 AND visiting_id = 10001 AND action=2
→ (visiting_id,visited_id,action)でマルチカラムインデックスは使われない
• Indexは貼られている順番に使わないと意味がない
WHERE visiting_id = 10001 AND action = 2 AND visited_id = 10002
→ (visiting_id,visited_id,action)というIndexの場合、visiting_idだけが使われる
• 順番に使われていないIndexは後続は無視
2011年3月5日土曜日
Covering Index
• インデックスの中だけで検索が完結するようなインデックス
MySQLでインデックスを使って高速化するならCovering Indexが使えそう - (゚∀゚)o彡 sasata299's blog
http://blog.livedoor.jp/sasata299/archives/51336006.html
• 複合キーのインデックスを貼っているテーブルの場合、取得するカラムを絞ると、Covering Indexになれるケースがあるので工夫してみる→常に必要なデータだけ取るようにする
2011年3月5日土曜日
Primary Keyは自動採番の単一idか、複数カラムか
• まず、Primary Keyを指定しないというのはありえない→必ず設定する。Primary Keyがない場合、MySQLは自動採番のidによるPrimary Keyをバックグラウンドで用意してしまうためMySQL :: MySQL 5.1 リファレンスマニュアル :: 13.5.13 InnoDB テーブルとインデックス構造http://dev.mysql.com/doc/refman/5.1/ja/innodb-table-and-index.html
• 単一idにすれば、WHERE id IN (a,b,c,d...)構文が使えるメリットがある
• 複数idにすると、インデックスをいくつも作っている場合、インデックスの肥大化の原因になるので、単一idのほうが望ましいが、検索する頻度が高いカラムであれば複合でもPrimary Keyにしたほうが良いケースも(後述するCovering
Indexを考慮)
2011年3月5日土曜日
Primary Keyはケースバイケース
• つまり、いろいろな検索の仕方をするテーブルの場合は採番idをPrimary Key
にして実際に検索に使うインデックスをそれぞれ貼るのがよく、1種類の複合キーでしか検索されないようなテーブルであれば、(クラスタインデックスが使えるので)複数カラムによるPrimary Keyが良いと考えられる
• 他のテーブルと合わせて検索されるケースが多い場合は単一のidで
• とにかく単一idのPrimary Key一発で取れるのが最強!(“WHERE user_id=10001”など)
2011年3月5日土曜日
採番テーブルとauto_increment
• 採番テーブル:例えば下記のようなテーブルを作り、整数が入っているレコードを一行だけ作り、それをひたすらカウントアップすることによって連番のIDを取得する
CREATE TABLE seq_history( seq_id bigint unsigned not null);
• auto_incrementを使う場合との違い:MySQL5.1以降ならパフォーマンスに大差なし。ただDBの水平分割をする際にはauto_incrementでは対応できないので採番テーブルを使う
2011年3月5日土曜日
ReadとWriteのバランスを考えて設計する
• Read(SELECT)はスレーブDBを並列に増やしていけばいいのでスケールが容易だが、WriteはマスターDBから順に全てのDBで実行する必要があるためコストが高い。
• 基本的にはWriteの量を減らし、SELECTしてそこから算出すれば済むものはいちいち書かない。
• 全ユーザー間のランキングなど、SELECTのコストがあまりに高いケースも存在するので、そういう場合は別個テーブルを作ったり、計算結果を格納するカラムを設けるのも考慮する必要がある
• ただし、インデックスを増やすとReadが効率的になる反面、データ量が増え、更新時にインデックスも更新するため、Writeのコストは上がる
2011年3月5日土曜日
短期的な負荷対策、長期的な負荷対策
• 短期的には、Read/WriteともにSQL実行の負荷を少なくすることにチューニングを最適化
• 長期的には、全体のデータ量が肥大化してきてハードウェアのメモリにおさまらなくなったとき、インメモリの処理からディスクIOの処理になってしまう。そうなったらデータベースのメモリ増設か、データベースの分割が必要になる。そうならないように全体のデータ量を少なく収める施策も必要
→不要なデータをパージ(削除)する、データ型を工夫する→データベース分割を見越した設計を行う
2011年3月5日土曜日
MySQLのレプリケーションとは何か
• マスターDBが実行した更新系クエリを記録し、スレーブDBが同じものを複製して実行することにより、DBをコピーする機能
マスターDB スレーブDB
Binary LogRelay Log
(1)更新系クエリ(INSERT,UPDATEなど)
はBinary Logに書き込まれる
IOスレッド SQLスレッド
(3)Relay Logに入っている更新系SQLが順に実行される
(2)IOスレッドがBinary
Logを読んでスレーブDBに伝達する
2011年3月5日土曜日
スレーブDBが遅延するわけ
• マスターDBの更新はマルチスレッド、スレーブDBの更新はシングルスレッド→MySQLの弱点の1つ
• マスターは更新系クエリが集中していて参照系クエリはあまりリクエストが来ない場合が多いが、スレーブはマスターからの更新(レプリケーション)を受けつつ、参照リクエストを処理しなければならないため、一般的にスレーブのほうが負荷は高い→そこで、スレーブだけSSDを使うと有効な場合がある cf.ビストランテ
2011年3月5日土曜日
レプリケーション時にやってはいけないこと
• 条件指定があいまいで、他のレコードに影響されて結果が変わりそうな更新系クエリの実行は絶対NG
UPDATE history_tbl SET state = 2 WHERE state = 1 LIMIT 10
→state=1のデータがどれか特定できないため、どのレコードが書き換わるか分からず、マスターとスレーブで不整合が起きる可能性がある
UPDATE history_tbl SET state = 2 ORDER BY finish_time LIMIT 1
→一見大丈夫に見えるが、finish_timeが同一のレコードがある可能性があり、大変危険
• UPDATE文は必ずWHERE句にPrimary Key指定で実行する
2011年3月5日土曜日