Relational Database
作成日時:2024-08-01以前
更新日時:2024-08-01
色々な製品
製品 | メモ |
---|---|
AlloyDB | HTAP |
Azure Cosmos DB | 分散 |
〃 for PostgreSQL | Citus(サイタス)を使った分散 |
Aurora | MySQL/PostgreSQLと互換性がある性能良い奴 |
〃 Serverless | サーバーレス |
Spanner | NewSQL |
TiDB | NewSQL |
CockroachDB | NewSQL |
最小・最小・最速であれ
- SQLの発行回数は最小
- 取得するデータ量は最小
- クエリは最速
システムが遅いのは外部APIの呼び出し。
つまりDBの所為。
なのでパフォーマンスを改善したければDBを何とかしろ
NULL
NULLはカス。
全カラムにNOT NULLつけろ。
集約関数系とか0件時とかの扱いが面倒くさい。
COALESCEを使え。
コーディング原則適用思想
コーディング原則に基づいた設計・製造をしろ。
- DRY原則
- (DML)CTE、VIEW
- (DDL)正規化、VIEW、算出可能カラムの排除
- KISS原則
- (DML)CTE、VIEW
- (DDL)正規化、VIEW
- YAGNI原則
- (DML)必要のないカラムを持ってくるな
- (DDL)計算で算出できるような情報はカラムとして持たない
- SRP原則
- (DDL)1カラムに複数の意味を持たせない
- SLAP原則
- 段落を揃えろ
- PIE原則
- 読みやすさを重視して書く
- 名前重要
- テーブル名やカラム名
- 小は美なり
- 発行回数
- 駆動表
- 取得するデータ量
SQLの大文字小文字
全部小文字で書いてもパフォーマンスはそこまで向上しないらしい。
可読性とルールを優先すべき。
クエリキャッシュを利用できるようにルールを徹底。
SQLのクエリキャッシュ
SQLそのものは大文字小文字の区別をしないがキャッシュは区別する
ルールを決めて書き方を統一。
クエリキャッシュは製品によってあったりなかったり。
ページング
フルスキャンになりがち。
データ件数が多いと、最初のページと最後のページで検索速度が大きく異なる。
検索するときは日付を必須にし、かつ「検索可能なのは1年の範囲だけ」とかにする。
要するに取り扱うデータ量を小さくする + インデックスを使用して走査しろ。
もしくは、検索結果の最後のid(もしくはソート対象)を付与しろ。
検索条件にそれ以降のデータを取るようにすればオフセットいらない。
異なった型でのWHERE
やめろ。インデックスが使われない。
DBの継承のやつ
実装方法は下記。
- 元のテーブルに両方の属性を持たせる
- 別のテーブルに分ける
- 共通属性を持つテーブルと特化した属性を持つテーブルを作成
VALUESの利便性
SELECT * FROM (VALUES(1,2),(4,5)) AS a;
WITH A AS (VALUES(A),(B))
日付型
データ格納時: 現在のシステムタイムゾーンからUTCに変換して保存される。
データ取得時: 保存されているUTCの値が現在のシステムタイムゾーンに変換されて返される。
MySQLはUTCで保持。
postgreSQLも。DB側でタイムゾーンの保持のありなしを選べる。
Java側の日付はoffsetDateTime。
NOW()は使うな。一括登録でずれる可能性があるし、サーバーの時刻に依存する。
postgreSQL WITH TIME ZONE + Java OffsetDateTime
H2
Java上で動くDB。
色々なRDBMSのSQLと互換性があるらしい。
その為、単体テストでのモックにいい。
レプリケーション
基本は1台か。
- master-slaveレプリケーション
- master(メイン)とslave(マスタの更新を随時反映)で構成
- masterが死んだらslaveにフェールオーバ
- slaveはホットスタンバイ。基本アクセス不可。
- master-masterレプリケーション
- masterが複数台でどれにでも更新できる。
- master間で同期してくれる。
- AWSはリードレプリカしかサポートしていない
- リードレプリカ
- 参照用のレプリケーション
- 参照するだけならそっちを見る
- リードレプリカを複数台にするならDNSラウンドロビンとか
- Springだとアノテーション使って切り替えられる。
- 参照系のAPIならレプリカアノテーションをつけたり
データ永続化の基本
- IOを減らす為、基本的にはメモリ上でデータをいじる。(ダーティページ)
- チェックポイントでディスクに保存。
- WALはコミットされたら即時。シーケンシャルなので早い。
PostgreSQL
- VACUUM:不要領域の回収。再利用可能にする
- VACUUM FULL:↑ + 不要領域を詰める。⇒ファイルサイズを減らせる。
- REINDEX:インデックス再編成
- CLUSTER:指定したインデックス順に物理データを再作成。
- EXPLAIN:実行計画
- EXPLAIN ANALYZE:実際にSQLを流して実行計画を見る
- ANALYZE:統計情報の更新
テンプレート
PostgreSQLのやつ。
新規DB作成時のもとになる。
クラスタ化インデックス
インデックス上に実データが格納されているもの。
MySQLのInnoDBなど。
InnoDB
基本的にPKのクラスタインデックスでデータを保持している。
リーフノードに実データを持っている。
PK以外のインデックスはセカンダリインデックス。
リーフに主キーの値が格納されている。
しかしセカンダリ⇒プライマリの2段階アクセスなのでパフォーマンスが問題。
つまりインデックスを2回見ている。
この主キー検索を高速化するためにアダプティブハッシュインデックスがある。
イメージとしてはkeyがセカンダリインデックスの検索値、valueがレコードへのポインタ。
ハッシュなので等価検索のときにのみ使われる。
バッファプール上に作られる。
ギャップロックとネクストキーロック
InnoDBのやつ。
格納されているレコードはx = {10,20,30}の3件とする。
SELECT * FROM tbl WHERE x = 20 FOR UPDATE;
= で検索して、存在する場合はレコードロック。
その行のみロックする。
上記の場合は20のレコードのみ。
SELECT * FROM tbl WHERE x = 19 FOR UPDATE;
= で検索して、存在しない場合はギャップロック。
実レコードの間をロックする。
上記の場合は11-19をロックする。
SELECT * FROM tbl WHERE x BETWEEN 18 AND 21 FOR UPDATE;
範囲で検索した場合、実レコード間のギャップと次の実データをロックする。
上記の場合は11-30をロックする。
ギャップロック同士は競合しない。
ギャップロックとネクストキーロックは競合する。
ロックに対して更新等は出来ない。
これにより、MySQLはREPEATABLE READでもファントムリードは発生しない。(関係ないけどPostgreSQLも)
検索した範囲内におけるデータ操作を禁止しているから。
ネクストキーロックはギャップ領域の拡大を防ぐためという解釈。
実存するインデックスデータでロックを管理しているのだから、データを消されるとギャップを管理できなくなる。
REPEATABLE READ以上かつFOR UPDATEなどのクエリで初めてこのロックが発生する。
MVCC⇒完全にファントムリードを防ぐ
ネクストキーロック⇒ロックのついでにファントムリードも実質防ぐ。
まあ、FOR UPDATEには気をつけとけ。ネクストキーロックが発生しうる。
GROUP BY (MySQL)
基本、一時ファイル
{GROUP BY指定カラム, 集計対象値}
でインデックスを貼っているとする。
タイトインデックススキャン
インデックスを使用してGROUP BYクエリを解決
ルースインデックススキャン
集計対象値のMINやMAXならば、各GROUP BY指定カラムの最初のインデックスデータを見ればすぐわかる。
(A, 1),(A, 2),(A, 3),(B, 4),(B, 5)
ならば先頭のカラムの値、つまり1と4のカラムだけを見ればいいから
パフォーマンスはよい。
MIN()を取ろうとすれば、A1,B4が返却される。
ロストアップデート
- (TX1)対象行ロック
- (TX1)バージョンカラムを更新
- (TX2)対象行のバージョンカラムの値が、画面で保持していたバージョンと異なる⇒上書きされているのでエラーとする。
悲観的ロックで、既にロックされていたら即時APでエラー返す。
分離性や楽観的ロックでは発生する。
後者は分離性上げてもMVCCで。
KISS原則
DBに限らんが、シンプルがベスト。
NoSQLは必要あるか。RDBで良いのでは。
マテビューは必要あるか。Viewで良いのでは。
マテビュー
- 更新可能なマテビューも製品によってはある
- 更新は手動更新が定期的な自動更新
- そのため情報はリアルタイムに反映されない。
- 参照系の操作が多い場合、更新時に手動更新すれば全体のパフォーマンスを下げず整合性が取れそう
NoSQL
- DynamoDB:key-value
- MongoDB:ドキュメント。JSONとかそのままぶち込む。
GROUP_CONCAT関数
- グループに紐づく対象項目を列挙する
SELECT id, GROUP_CONCAT(col, '+') FROM tbl GROUP BY id;
=> 1, col1+col2+col3
再帰With
基本遅いから使うな。
駆動表が小さい時だけ使え。
2PL
- よく使うのが保守的な2PL
- 最初に全部決められた順でロックする
- 厳格な2PLはデッドロックになる
イベントソーシングとマテビュー
イベントをDBに流し込んで、夜間バッチで参照可能な形にマテビューを構築するとか。
普通のビューでも良さそうだが、マテビューはリフレッシュコマンド打つだけだから楽そう。
日時を表すカラム
XXX_onは日付、XXX_atは日時。
SQLの評価順序
- FROM
- JOIN
- WHERE
- GROUP BY
- HAVING
- SELECT
- DISTINCT
- ORDER BY
- LIMIT/OFFSET
大きいデータの除外
TEXTやBLOBは別テーブルに切り出すのもあり。
SELECTした場合、使う使わないにかかわらず、関係なくロードされる。
行指向であるため、関係のないデータも読み込むから。
結合を頻繁にするならそうでもない。
テーブル設計
下記がテーブルの対象となる。
- ヒト・モノ・コト
- 5W1H
- イベントとリソースに分ける
- イベントは時刻を持つ
- 前者は動詞、後者は名詞
トランとマスタ
不要。
いちいちt_とか名称にXXマスタとか不要。
設計
- パフォーマンスに直結する
- 変更が困難。思わぬところに影響が出る。
だからDB設計はちゃんとやらなければならない。
マルチテナントにおけるRLS
その他
NoSQLにおけるスキーマ定義の重要性
redisかます。
- 実行計画を取る時は実運用上、発生しうるデータで行う
- EXPLAIN ANALYZE使え
- ページサイズとバッファ
- インビジブルインデックス
- 使わないようにするだけ
- 実際には消されない
- 削除・再作成に時間がかかるから消すだけ
- 検証用
- MySQLはマテビューない
- 部分インデックス
- 付加列インデックス
- MySQLはシングルプロセスマルチスレッド、PostgreSQLはマルチプロセス