失敗から学ぶRDBの正しい歩き方
https://gyazo.com/d0118f714b20815ca5939a2660ec66e3
メモ
同じデータを別のカラムに自動で移す
DBのリファクタリングをする際、カラムを移行すると変更範囲が大きいため同じ内容のカラムを用意することがある
アプリ側の実装が大幅に削減できるときとか
あくまでも顕著なメリットがある場合のみ採用を考えるべき
アンチパターン
後からデータを遡りたいとき、事実が失われるような設計にしてしまう
例
払い戻しの実装ができるか
ステータスの変化を追えるか
トラブルに対応したときに欲しい情報が失われていないか
一方、履歴を全て残していくとデータ量が大きくなりパフォーマンスが劣化する
DBの範囲で厳しい場合は別の手段で履歴を残しておくと良い
ebiken.iconステータスカラムを持つテーブル作りがち..
複雑なクエリを減らす
使う際にクエリを再実行するので高速化にはならない
ebiken.iconどういうメリットがあるのかよくわかってない
インデックス
where 狙い
order by 狙い
indexはソートされた状態で保存されているので、ソートしたいカラムのカーディナリティが高いときに有効
検索結果がテーブル全体の20%未満
対象のテーブルが大きい
使われない例
code:sql
SELECT * FROM users WHERE age*10 > 100;
↓のようにする
SELECT * FROM users WHERE age > 100/10;
部分一致のindex
闇雲にインデックスを張ってしまう
ebiken.icon付けても意味ないindexを今まで付けてた。whereとorder byで使うやつは全部つけてた
とりあえず削除フラグ作ってしまう問題
クエリの複雑化
親テーブルの状態で絞るときにINNER JOIN ... ON deleted=falseとか
UNIQUE制約が使えなくなる
カラムに含まれている種類
実行計画見よう
ソートはメモリ上に収まるならクイックソートだが、大きくなるとファイルに書き出す外部ソートになり遅くなる 処理の結果をRedisに保存してそこを参照する構成はよくある IDに識別できる一意の値以外の意味をもたせてはいけない
似たような属性だと一つのテーブルに保存しがち。分けたほうが良いことが多い
MySQLでは外部キー制約が貼ってある子の更新時に親の共有ロックを取る。 子と親を同じトランザクション内で更新するとデッドロックになる code:_
-transactionA- 子の追加
-transactionB- 子の追加
-transactionA- 親の更新 (Bの子の追加を待つ)
-transactionB- 親の更新 (Aの子の追加を待つ)
dead lock
ALTER文による更新
ACCESS EXCLUSIVEという一番強いロックを取り、インデックスが貼られている場合すべて構築し直す。そのため巨大なデータが格納されている場合サービスを止めざるを得なくなる
RDB ENUM型は表記ゆれに対して強い制約になるが、ALTERが必要なため仕様変更に弱い updated_atが現在時間より後というような制約をつけた場合、ロールバックできなくなってしまう
現在時間という状態をCHECK制約に使うのは良くない
多くのシステムは適切な正規化と弱い制約で十分に設計できる。強すぎる制約になっていないか意識する必要がある
バックアップをちゃんと定期的にとっておき、障害時に戻せるような練習を定期的に行う
エラーログの運用設計をシステム設計時に行う
ebiken.iconRDSとかCloud SQLみたいにマネージドサービスを使っているときはコンソールとかから設定しておけば良いので楽 ebiken.iconスロークエリログはSlackに自動で流すとか、ちゃんと監視の仕組みは整えておく
ロック
レベル
すべてのアクセスを禁止
参照以外のアクセスを禁止
粒度
表(テーブル)ロック
行(レコード)ロック
ロックの振る舞いはRDBMSごとに違うので把握しておく必要がある
キャッシュ
頻繁に更新するならパフォーマンスの低下に繋がり、更新頻度が低いならアプリケーションレイヤーや他のデータストアに置くほうが効率的
config
フレームワークとの関係
STIなど、フレームワークに依存した実装をするときはアプリケーションの実装によってデータの不整合が生じてしまうことがあるため注意 テーブル設計がビューに依存すると正規化した状態とは違うものになることがある