じぶん対策

日々学んだことをアウトプットして備忘録にしています。

データベース知識基礎まとめ

はじめに

今回はデータベース周りを勉強するにあたって頻出する単語や知識についてまとめていきたいと思います。
現在データベース周りに課題を感じていて、チーム内で話していても自分のデータベース周りの知識が足りていないなと感じているため直近の課題だと思っています。
今回は以下の内容についてまとめていきます。

ORMについて

ORMとは、Object-Relational Mappingの略です。

データベースとオブジェクト指向プログラミングの間のデータを変換する方法。
つまり、データベースに対するデータの操作をオブジェクト指向型言語の操作方法で扱えるようにします。

オブジェクト指向...現実世界をモデル化したもの

関係データベース...検索やCRUD処理に最適化されたデータのためのモデル。

この二つの間には考え方の違いがあります。(インピーダンスミスマッチというらしいです)

ORMを用いれば直接SQLを書くことなく、オブジェクトのメソッドを用いてDB操作ができる。

ORMのメリット

ORMのデメリット

  • 各ORMライブラリの使い方を覚えないといけない
  • 直接SQLを書くわけではないのでチューニング等の面に課題がある

トランザクションとは

参考: https://gihyo.jp/dev/serial/01/db-academy/000201

RDBにおけるトランザクションとは簡単にいうと、複数のSQL文の実行を1つの処理としてまとめた単位のことです。 これはあくまでも人間が考える単位であり、DBMSで自動的に判別できるわけではないです。

この単位を分けるための仕組みとして、復旧する単位を設定するためにトランザクションという仕組みがあります。

DBMSから見るとトランザクションにはデータの復旧同時実行制御の二つ機能に関係しています。

データの復旧

システム障害が発生した時、障害発生前に終了しているトランザクションの結果は保証するようにDBMSは作られています。 これがトランザクションが満たすべきACID特性(後述)の一つである耐久性です。

では、システム障害が発生したときにトランザクションが実行途中の場合を考えてみます。 この場合、DBMSトランザクションの開始前までロールバック、つまりなかったことにします。 この、トランザクションの前後で必ずトランザクションが完了している、もし途中の場合はなかったことにするというのがACID特性の一つ、原子性です。

トランザクションが終わるタイミング

本来であればこの辺りの説明にとどめておこうと考えていたのですが、参考記事に勉強になりそうな内容があったため、そちらについてもまとめておきます。

トランザクションは、COMMITもしくはROLLBACKが実行されたときに終了します。 しかし、多くのDBMSの内部処理ではデータファイルへ変更を反映する前に、トランザクションの終了をユーザに通知します。 ということは、COMMIT後のデータファイルへの反映前のタイミングで障害が発生した場合はどうなるのでしょう。

この問題を防止するために、DBMSにはWALという仕組みが用意されています。
WAL(write-ahead log)とは、ログ先行書き込みと訳され、COMMIT時にデータファイルに全ての変更を反映するのではなく、ログファイルにトランザクションで行われた操作の記録を書き出す方法です。
データファイルに変更を書き込むより短時間で終わることがメリットです。
WindowsNTFS(NT File System)やLinuxファイルシステムでも用いられているらしいです。

このログファイルさえ残っていれば、ログファイル通りに再実行して正しい状態に変更できます。 この処理をロールフォワードと呼びます。この辺りの知識は応用情報技術者試験の勉強中に頻出した記憶があります。

同時実行制御について

データベースは基本的に一人で占有していることはありません。
自分以外の人の操作を同時並行で実行しています。
ですがデータベースを扱う際に私たちは他人の存在を意識せずに操作することができます。
これはDBMSが複数のユーザの処理をスケジューリングし、結果の整合性を担保してくれているおかげです。
この性質はACID特性のうち、独立性と呼ばれる性質です。

直列させて分離性を担保する

複数のトランザクションが同時実行されている場合、「正しい」状態とは、どのような状態でしょうか。
3つのトランザクションが順次実行された場合と得られる結果が等しい場合に正しいということが言えそうです。
つまり、並列的な考えではなく、直列化して考えることができればこの「正しい」状態を得ることができます。
ただ、実際に直列させて実行した場合は著しいパフォーマンス低下を引き起こします。
そこでDBMSが取り入れている方法がロックによる解決です。

ロックによる解決

  • 排他ロック

テーブルのある行を更新する場合にその行に対するほかのトランザクションのアクセスを一切禁止する方式。

  • 共有ロック

ほかの共有ロックと両立する方式。 ただし、共有ロックとはいえ、排他ロックとは両立することはできません。

スラッシング

DBMSがロックを行うとき、並行トランザクション数が一定数を超えると、1つのトランザクションが待機させられる頻度と時間が増え、平均パフォーマンスが悪くなります。 ロックによってパフォーマンス低下が起きる現象をスラッシングと呼びます。

対策

  • 限界多重度を超えないように流量制限を行う
  • ロックの粒度を小さくする

ロックのコストを下げる方法

トランザクションが直列化可能ではないことを認めて、いい加減な結果に我慢するという妥協案を取ります。 多くのDBMSがこの方法を採用していて、直列化可能であるよりも低いレベルの分離性をサポートしています。

参考: https://ja.wikipedia.org/wiki/%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B6%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E5%88%86%E9%9B%A2%E3%83%AC%E3%83%99%E3%83%AB

ANSI標準では、次の四つのトランザクション分離レベルが定義されています。 下に行くほど厳しく、直列化可能はトランザクション同士の干渉を許しません。 上に行くほど他のトランザクションの干渉を受けやすくなります。

  • READ UNCOMMITTED 非コミット読み取り
  • READ COMMITTED コミット済み読み取り
  • REPEATABLE READ 再読み込み可能読み取り
  • SERIALIZABLE 直列化可能

直列化可能以外では、次のような正常ではない現象が起きます。

  • Dirty Read ダーティリード

Taが列の値を変更しているが、コミットしていない状態でもTbが変更後の値を読み出してしまう。 確定前の「汚れた」データを読み出してしまうことからダーティリードと呼ばれる。

  • Non-repeatable Read 繰り返し不可能な読み出し

Taがある列の値「10」を読み出す。 その後Tbが列の値を「20」に変更しコミットを行う。 その後Taが再度SELECTを行なった場合、変更後の「20」が読み出されてしまう。同じトランザクション中で読み取り結果が再現しないことから名前がつけられた。

  • Phantom ファントム

Taが範囲検索を行い、3行のデータを読み出す。 その後、Tbがその範囲に収まるデータを1行INSERTし、コミットを行う Taが再度同じSELECT文を実行すると選択されるレコード数が4行になる。(正しくは最初と同じ3行のみであるはず) 消えたり現れたりするデータが「幽霊」に似ていることからついた名前。

これらの現象とトランザクション分離レベルの関係

分離レベル ダーティリード 繰り返し不可能な読み出し ファントム
非コミット読み取り Y Y Y
コミット済み読み取り N Y Y
再読み込み可能読み取り N N Y
直列化可能 N N N

直列化可能の場合は一切干渉が起きないため、これらの現象とは無縁です。 ほとんどのDBMSでは、「コミット済み読み取り」をデフォルトの分離レベルとしています。
ちなみにMySQLは少し厳密で「再読み込み可能読み取り」をデフォルトの分離レベルとしているそうです。
このあたりがパソーマンスと厳密さのトレードオフをどの程度にするかというDBMSごとの考えの差になります。

参考: https://dev.mysql.com/doc/refman/8.0/ja/innodb-transaction-isolation-levels.html

ACIDについて

DBに関連する書籍等を読むと頻出するACID特性について。 先述したトランザクションの説明でも軽く触れましたが改めてまとめておきます。

  • Atomicity 原子性
  • Consistency 一貫性
  • Isolation 独立性
  • Durability 耐久性

の頭文字をとってACIDです。 これらの性質についてのまとめと、実際にDBでどうやってこの性質を満たしているかについて調べてみます。

Atomicity 原子性

トランザクションに含まれる個々の手順が
全て実行される or 何も実行されない
のどちらかの状態になるという性質。

処理の途中で中断されても問題ない、という性質とも言えます。

Consistency 一貫性

整合性とも訳されることがあります。 トランザクションの前後でデータの整合性が保たれ、矛盾が生じないという性質。

Isolation 独立性

トランザクション実行中の処理過程が外部から隠蔽され、他の処理に影響を与えない性質。

Durability 耐久性

トランザクションが完了したら、その結果は記録され、障害などで失われることがない性質。

N + 1問題について

調べていると「1 + N問題」って呼べ派閥の人がいて確かにそっちの方がわかりやすいかもなあと思いました。

N + 1問題とは要するに

  • 「全レコードの取得に一つ+各レコード分(N)」だけSQLを発行してしまう問題

のことです。

これが発生する状況としては

  • 何かのデータ一覧を取得する場合に全体を取得するためのSELECTを1回実行
  • 各レコードに対して関連データの取得のためにSELECTをレコード数(N)回実行

のようなパターンです。 結果としてN + 1回のSQLが発行されるためにこう呼ばれます。
適切なタイミングでJOINできずに無駄なSQLの実行が発生するため、著しいパフォーマンスの低下が発生します。
また、データ量が増えるに従って爆発的に処理にかかる時間が増加します。
開発時の少ないデータ数では問題にならなくても本番環境で問題になるケースがあります。

まとめ

今回はデータベース知識の基礎部分についておさらいしました。
トランザクション分離レベルに関しては初見の知識も多く、勉強になりました。
データベースはWebサービスの基本であると同時にさまざまな技術によってカプセル化されている部分でもあるので、しっかり中身の処理を理解した上で使用していきたいと思います。
DBスペシャリスト試験も来年くらいには受験したいですね。(今年の申し込みは忘れた)