Relational Database
作成日時:2024-08-01以前
更新日時:2025-01-18
色々な製品
製品 | メモ |
---|---|
AlloyDB | HTAP |
Azure Cosmos DB | 分散 |
〃 for PostgreSQL | Citus(サイタス)を使った分散 |
Aurora | MySQL/PostgreSQLと互換性がある性能良い奴 |
〃 Serverless | サーバーレス |
Spanner | NewSQL |
TiDB | NewSQL |
CockroachDB | NewSQL |
NULL
NULLは撲滅する。
全カラムにNOT NULLつけろ。
集約関数系とか0件時とかの扱いが面倒くさい。
COALESCEを使え。
「どうしてもNULLになるからデフォルト値を入れよう」
っていうのもやめろ。
不要なフラグ
やめろ。
2値で表現できないからって、フラグをたくさん作るな。
区分作って1カラムで表現しろ。
コーディング原則適用思想
コーディング原則に基づいた設計・製造をしろ。
- 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の継承のやつ
実装方法は下記の3パターン。
- 両方の属性を持ったテーブルを作る(単一テーブル継承)
- それぞれ別のテーブルに分ける(具象テーブル継承)
- 共通属性を持つテーブルと特化した属性を持つテーブルを作成(クラステーブル継承)
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はコミットされたら即時。シーケンシャルなので早い。
ロストアップデート
- (TX1)対象行ロック
- (TX1)バージョンカラムを更新
- (TX2)対象行のバージョンカラムの値が、画面で保持していたバージョンと異なる⇒上書きされているのでエラーとする。
悲観的ロックで、既にロックされていたら即時APでエラー返す。
分離性や楽観的ロックでは発生する。
後者は分離性上げてもMVCCで。
KISS原則
DBに限らんが、シンプルがベスト。
NoSQLは必要あるか。RDBで良いのでは。
マテビューは必要あるか。Viewで良いのでは。
マテビュー
- 更新可能なマテビューも製品によってはある
- 更新は手動更新が定期的な自動更新
- そのため情報はリアルタイムに反映されない。
- 参照系の操作が多い場合、更新時に手動更新すれば全体のパフォーマンスを下げず整合性が取れそう
NoSQL
- DynamoDB:key-value
- MongoDB:ドキュメント。JSONとかそのままぶち込む。
- HBase:ワイドカラム
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は日時。
大きいデータの除外
TEXTやBLOBは別テーブルに切り出すのもあり。
SELECTした場合、使う使わないにかかわらず、関係なくロードされる。
行指向であるため、関係のないデータも読み込むから。
結合を頻繁にするならそうでもない。
テーブル設計
下記がテーブルの対象となる。
- ヒト・モノ・コト
- 5W1H
- イベントとリソースに分ける
- イベントは時刻を持つ
- 前者は動詞、後者は名詞
トランとマスタ
不要。
いちいちt_とか名称にXXマスタとか不要。
設計
- パフォーマンスに直結する
- 変更が困難。思わぬところに影響が出る。
だからDB設計はちゃんとやらなければならない。
HWM(High Water Mark)
- セグメントの使用済み領域の終端。未使用領域との境界。
- データ追加(行追加やインデックス更新)したりすると増える。
- DELETE等でレコードが削除されても、基本減らない。
- HWMと実際の使用領域に乖離があると、断片化している可能性がある。
- セグメント単位でメモリにロードするならば、不要なスキャンが発生している。
リンク
- DELETE文とTRUNCATE文の違い〜HWMの引き下げ - 日常メモ
- データファイルのハイウォーター・マーク(最高水位標/High-water Mark/HWM)を確認する | SSJ Tech Lab
- MySQL InnoDBの領域管理 #MySQL - Qiita
DBのデータ構造(要検証)
テーブルスペース -> セグメント -> エクステント -> ページ -> レコード。
1. テーブルスペース (Tablespace)
- データベース内の最大の論理的な記憶単位です。
- 1つ以上の物理データファイルで構成されます。
- 複数のセグメントを含むことができます。
- データベース管理者が管理し、データの物理的な配置を制御するのに役立ちます。
2. セグメント (Segment)
- 特定のデータベースオブジェクト(テーブル、インデックスなど)に割り当てられた記憶領域です。
- 1つ以上のエクステントで構成されます。
- 例:テーブルセグメント、インデックスセグメント、一時セグメントなど。
3. エクステント (Extent)
- 連続した複数のデータブロック(ページ)の集まりです。
- セグメントに割り当てられる最小単位です。
- データベースの拡張時、セグメントに追加される単位となります。
4. ページ (Page) またはブロック (Block)
- データベースのI/O操作の最小単位です。
- 固定サイズで、通常は4KBから16KBの範囲です(データベース製品により異なる)。
- 複数のレコードを含むことができます。
- データベースがメモリとディスク間でデータを転送する際の単位となります。
5. レコード (Record)
- データベース内の個々のデータエントリです。
- テーブルの1行に相当します。
- フィールド(列)の集合で構成されます。
- データベースの最小の論理単位です。
ORMにおけるクラス分割
下記の感じ。
単純なテーブル操作は”テーブル名 + Dao”クラス。
複数のテーブルが絡んだり、ドメインに紐づく者は”ドメイン名 + Dao”クラス。
- リポジトリパターンとの組み合わせ
- クリーンアーキテクチャへの適合
- マイクロサービスでの境界の明確化
TEXT
- TEXT型の大きいデータ(行/カラム)は、TOASTメカニズムによって別テーブルに保存される。
- データサイズが2KB(2048バイト)を超える場合
- 圧縮可能な場合は、まず圧縮を試みる
- それでも大きい場合は分割して格納
文字長さに応じて、格納するデータサイズが異なる。
大小の閾値は2KB。
大きいTEXT = TOASTポインタ(18Byte) + 文字列長(4Byte)
小さいTEXT = 実際の文字列サイス + 文字列長(4Byte)
fillfactor
PostgreSQLはデフォルトで100。
100以下にした場合で、更新した場合、そのページ内に追記される。
すなわち新しくページを作らなくてよいので、多少パフォーマンスがよくなる。
boolとsmallint
t / fの2値よりは、なるべく多い方がいいか。
1Byte増えるけどsmallintで。
1ページ(8KB)に収まるなら
行サイズ = タプルヘッダ(23バイト)
+ NULLビットマップ(必要な場合)
+ データ(アライメント込み)
+ パディング(8バイト境界に合わせる)
タイムアウト
postgresql
デフォルトは永久にロック解除を待つ。
- lock_timeout
- ロック待ちのタイムアウト
- statement_timeout
- クエリ実行のタイムアウト
- 最適な実行計画が得られなかった場合の時間稼ぎにも
- idle_in_transaction_timeout
- トランザクション中のアイドル時間
-- セッションレベルでのタイムアウト設定 (秒単位)
SET statement_timeout = '30s';
-- データベースレベルでのタイムアウト設定
ALTER DATABASE your_database SET statement_timeout = '30s';
-- サーバー全体でのタイムアウト設定
ALTER SYSTEM SET statement_timeout = '30s';
ディスク容量不足でロックが解除されない
発生しうる。
- DBにロック。暗黙的でも明示的でも。
- 何らかのファイルを作成。
- しかし、ディスク容量がいっぱいなので作成不可
- この時に、1のロックがずっと解除されない可能性あり
対策
- タイムアウトの設定
- ディクス容量の監視
集約関数
グループ内の全てのレコードが条件を満たすかの判定
R(id, sub_id, created_at)
主キーは(id, sub)
上記のテーブルから、id単位でcreated_atが特定の時間以前であるレコードを削除。
例えば、下記のレコードがある。
1,1,12/12
1,2,12/11
2,1,12/11
2,2,12/11
12/11以前を条件とした場合、idが1のレコードは消えず、2のレコードは消える。
DELETE FROM R
WHERE id IN (
SELECT id
FROM R
GROUP BY id
HAVING MAX(created_at) <= NOW()
);
DBにまつわるAIとエンジニア
今後、SQLはAIを使った方が正しいクエリを出せると思う。
そうなった場合、エンジニアはAIに投げる正しい命題を作る能力が重宝されると思うがどうか。
構造
- MySQL
- インスタンス⇒スキーマ⇒テーブル
- PostgreSQL
- インスタンス⇒データベース⇒スキーマ⇒テーブル
- 1PostgreSQLサービスに対して1インスタンス(DBクラスタ)
- 複数サービスを起動すれば、1ホストで複数のインスタンスを扱える。
すごい大雑把な比較
MySQL:シンプル、速い
PostgreSQL:高機能、複雑なクエリ◯、ちょっと遅い、リソースを食う
小規模システムならMySQLのほうがいいのか?
RDSのインスタンスをスケールダウン出来るんじゃない?
後はクラスタと追記型アーキテクチャとか。
階層型DB
金融系とか。
大量データを扱う
- そもそも扱うな
- 最少・最少・最速の原則
- ベースとなる表(駆動表とか)は小さく
- EXISTSはベースとなる表の分だけ動くぞ
- IN区内の問い合わせは小さく
- その問い合わせはメモリ上に確保されるから
- index使われるように
- オブジェクトサイズは事前に確保しとけ
- Javaのコレクションみたいに足りなくなったら徐々に追加するから⇒遅い
- 実行計画も最悪なものになる可能性が高い。
- 事前にダミーデータを突っ込んで、統計情報を更新とかする。
- ログバッファの拡張
- 共有バッファのチューニング
統計情報(PostgreSQL)
下記を参照。
- pg_stats
- pg_statistic
- pg_stat_user_tables
更新/チューニング
デフォルトで自動更新はされる。(autovacuum_enabled)
細かくチューニングしたい場合は下記。
autovacuum_max_workerとかも。足りないと自動vacuumが遅れるかもしれない。
20.10. 自動Vacuum作業
25.1. 定常的なバキューム作業
手動も可能。
コマンド例は下記。
-- PostgreSQL
-- テーブル全体の統計情報を更新
ANALYZE table_name;
-- 特定のカラムの統計情報を更新
ANALYZE table_name(column1, column2);
-- データベース全体の統計情報を更新
ANALYZE VERBOSE;
大量データ投入時とかに手動実行する。
統計情報が古いと最適なクエリが発行されない。
凍結(固定化)
PostgreSQLは完全な凍結がないっぽい。
pg_dbms_statsを使えばいいけど。
autovacuum_naptimeのデフォルトが1分ということは、
大量にデータを投入して1分以内に検索を行うと、古い統計情報に基づいたクエリが発行される。
それでCPUが100%になったら、ANALYZEはなかなか起動or完了しないかも。
VACUUMに仕様するメモリや、ワーカーを増やす。
ALTER TABLE t SET (autovacuum_enabled = false)
で自動VACUUMを止められるらしいが、手動ANALYZEや再起動で更新されるらしい。
そもそもVACUUM(+ANALYZE)を停止するのは推奨されていない。
クエリの安定化には、デューン/砂の惑星ダミーデータ投入+自動VACUUMのチューニングが良いのでは。もしくはヒントを使う。
PostgreSQLの実行計画の推定行数と実行数の乖離改善の考え方
パラレルスキャンのチューニング
parallel_setup_costとparallel_tuple_costは小さくすれば、パラレルになりやすいらしい。
下記のページでparallelがついているものを調整する。
高速なクエリより安定したクエリ
データ件数の変動で急激に遅くなるよりかは、どんな件数でも安定した実行計画が出るようなSQLがいいかもしれない。
パーティショニング
パーティショニングの概要|PostgreSQLインサイド : 富士通
シャーディング
パーティショニングは同一DBのインスタンス内、シャーディングは複数DBのインスタンスにデータを分散。
という認識でいる。
JSON
挿入のJSON、検索のJSONB
論理学
x∈y:xはyに属する。
x⊃y:xならばy。
x⊆y,x⊂y:xはyの部分集合。
∀xf(x):全称量化子:集団の全要素がf(x)を満たす。
∃xf(x):存在量化子:集団の中にf(x)を満たす要素が存在する。
カラム追加よりテーブル追加を
SAVEPOINT
部分的なロールバックが可能。
データの一貫性
アプリケーションロジックが正しいことを前提とし、パフォーマンスを無視した場合、
楽観的同時実行制御と悲観的同時実行制御を組み合わせることで、理論上はデータの一貫性を常に保つことができる。
RDBと関係モデル
名称の対応。
タプル、組、ロー:行
アトリビュート、属性、カラム:列:
定義域(ドメイン):属性の取りうる値のこと
次数:属性の数
基数:タプルの数
REPEATABLE READでファントムリードが発生しない
だいたいMVCCのおかげ。
MySQLとPostgreSQLにおいて、REPEATABLE READでファントムリードが発生しないのは、スナップショットを見ているため。
PostgreSQLは追記型アーキテクチャによって。
MySQLはUndoログによって。
自身のトランザクションIDをキーに見るべきデータを見ている。
※MySQLはMVCCに加えて、ギャップロック、ネクストキーロックも関わる。
その他
NoSQLにおけるスキーマ定義の重要性
redisかます。
- 実行計画を取る時は実運用上、発生しうるデータで行う
- EXPLAIN ANALYZE使え
- ページサイズとバッファ
- MySQLはマテビューない
- MySQLはシングルプロセスマルチスレッド、PostgreSQLはマルチプロセス
- 一時テーブル(temporary table)
- セッション中有効
- SQL ServerのCDC(Change Data Capture)
- データの変更内容を取得できる
- ロングトランザクションの禁止
- 統計情報の更新タイミング設定
- 当たり前だが、結合条件がNULL=NULLは抽出されない
- 空集合(くうしゅうごう)