mnagaaのメモ

技術的なことはこのブログで書きます

データベース #1 ~基礎編~

データベースについて

場合によっての分類

場合によっては、以下のように分類されることがある。昨今はOLTPもOLAPのように動作タイプを変更できるようにもなっているので、詳しくはDBごとの更新を確認してほしい。

1. OLTP(Online Transaction Processing)データベース:

  • 特徴: 高頻度で少量のデータを迅速に処理するために設計されている(少量 is ...?)
  • 用途: 取引処理、注文処理、銀行のトランザクションなど、リアルタイムでのデータ入力や更新が必要なシナリオで使用される。
  • : MySQLPostgreSQLOracle Database

2. OLAP(Online Analytical Processing)データベース:

  • 特徴: 大量のデータを迅速に分析するために設計されている。複雑なクエリを効率的に処理できるよう最適化されている。
  • 用途: ビジネスインテリジェンス(BI)、データウェアハウス、データマートなど、意思決定をサポートするためのデータ分析やレポート作成に使用される。
  • : Amazon Redshift、Google BigQuery、Snowflake

3. HTAP(Hybrid Transactional/Analytical Processing)データベース:

  • 特徴: OLTPとOLAPの機能を組み合わせたデータベース。リアルタイムでのトランザクション処理と、リアルタイムでのデータ分析の両方をサポートしている。
  • 用途: リアルタイムのデータ分析が求められるシナリオで、トランザクションと分析を同時に行う必要がある場合に使用される。
  • : SAP HANA、MemSQL(SingleStore)、Google Cloud Spanner

結局は、どういうユースケースで使うかが大事。 行志向DBとか列志向DBなどの分類もあるので、別のブログで書きます。

補足

MySQL Heatwave

MySQL Heatwave

MySQL :: MySQL HeatWave: One Database for OLTP, OLAP, ML & Lakehouse

MySQL Heatwave
データ分析におけるPostgreSQL、MySQLの今 / PostgreSQL and MySQL in data analysis - Speaker Deck

HSAP(Hybrid Serving/Analytical Processing)

HSAP

Benefits and Value of Hybrid Serving/Analytical Processing (HSAP) | Medium


クライアントサーバーモデル

1. 基本的な概念:

  • クライアント: データベースにアクセスして操作を要求するユーザーやアプリケーション。データベースへのクエリやデータの操作を行う側を指す。
  • サーバー: データベース管理システム(DBMS)が動作するコンピュータやシステム。クライアントからの要求を受け取り、処理し、結果を返す。

2. 動作の流れ:

  • リクエス: クライアントがデータベースサーバーに対してクエリやトランザクションのリクエストを送信する。
  • 処理: データベースサーバーがリクエストを受け取り、クエリを解析し、データベースに対して操作を行う。
  • レスポンス: データベースサーバーが操作結果を生成し、クライアントに対して結果を返す。

3. 利点:

  • 分散処理: クライアントとサーバーの役割を分けることで、処理を分散させることができる。
  • セキュリティ: データベースサーバーがクライアントからのアクセスを集中管理するため、セキュリティ管理が容易になる。
  • スケーラビリティ: クライアントの数が増加しても、サーバー側でスケーリングすることで対応できる。(Readは割と簡単だけどWriteはね...)

4. :

  • ウェブアプリケーション: ユーザーがブラウザ(クライアント)を通じてウェブサーバーにアクセスし、ウェブサーバーがデータベースサーバーにリクエストを送る。
  • 企業システム: 社内のクライアントコンピュータが、中央のデータベースサーバーに接続して、業務データを操作する。

クエリプロセッサ

クエリプロセッサは、データベース管理システムにおけるクエリの実行において重要な役割を果たすコンポーネントである。 クエリプロセッサは主に二つの主要な部分に分かれる。

1. クエリパーサ(Query Parser):

  • 役割: クエリパーサは、クライアントから送信されたSQLクエリを解析し、その構文と意味を理解します。
  • プロセス:
    1. 構文解析: クエリの構文をチェックし、SQL文が正しい形式で書かれているかを確認します。構文エラーがある場合、エラーメッセージを生成します。
    2. 構文木の生成: 正しい構文で書かれている場合、クエリを構文木(パースツリー)に変換します。この構文木は、クエリの各要素の関係を表現します。
  • 出力: 構文木。この構文木は次のステージであるクエリオプティマイザに渡されます。

2. クエリオプティマイザ(Query Optimizer):

  • 役割: クエリオプティマイザは、クエリの実行計画を最適化するために、複数の実行方法を評価し、最も効率的な実行計画を選択する。
  • プロセス:
    1. 評価: クエリの構文木をもとに、様々な実行計画を評価する。これには、異なるインデックスの使用、結合順序の変更、アクセスパスの選択などが含まれる。
    2. コスト見積もり: 各実行計画のコストを見積もる。コストは一般的に、必要なI/O操作の回数、CPU時間、メモリ使用量などに基づいて評価される。
    3. 最適化: 最も低いコストを持つ実行計画を選択する。これにより、クエリの実行速度が最適化される。
  • 出力: 最適化された実行計画。この実行計画は次のステージである実行エンジンに渡され、実際にクエリが実行される。

クエリプロセッサの流れ

  1. クライアントからクエリが送信される
  2. クエリパーサがクエリを解析し、構文木を生成する
  3. クエリオプティマイザが構文木を最適化し、実行計画を生成する
  4. 最適化された実行計画が実行エンジンに渡され、クエリが実行される

クエリパーサはクエリを正確に解析し、クエリオプティマイザはそのクエリを最も効率的に実行する方法を見つける。 このプロセスにより、データベースシステムはパフォーマンスを最大化し、ユーザーに迅速なレスポンスを提供することができる。

構文木(パースツリー)

構文木(パースツリー)は、SQLクエリの構文解析結果をツリー構造で表現したもの。 各ノードはクエリの要素(例えば、SELECT、FROM、WHERE句など)を表し、ノード間の親子関係は要素間の構造的な関係を示す。

具体例

SELECT name, age FROM users WHERE age > 30;

構文木は次のようになる(DBによってもおそらく異なる)

                SELECT
               /      \
          COLUMNS      FROM
         /       \       |
     name       age    users
                       /     \
                    WHERE     >
                      /      / \
                    age     age  30

構文木の説明

  1. SELECTノード: ツリーのルートノードはSELECT文を表します。
  2. COLUMNSノード: SELECT文の次のレベルに、選択する列(name, age)を示すノードがあります。
    • nameノード: name列を示します。
    • ageノード: age列を示します。
  3. FROMノード: データを取得するテーブル(users)を示すノードがあります。
  4. usersノード: usersテーブルを示します。
  5. WHEREノード: WHERE句を示すノードがあります。
    • 条件ノード(age > 30): WHERE句の条件を示します。
      • ageノード: age列を示します。
      • >ノード: age列に対する条件(30より大きい)を示します。
      • 30ノード: 比較対象の値(30)を示します。

実行計画の例

実行計画は通常、各ステップの順序と使用するインデックスや結合方法などの詳細を含む。 以下は、このクエリに対する典型的な実行計画の例です。

  1. テーブルスキャンまたはインデックススキャン:

    • まず、usersテーブルからage > 30の条件に一致する行を検索する。
    • インデックススキャン: age列にインデックスがある場合、インデックスを使用して効率的に条件に一致する行を検索する。
    • テーブルスキャン: インデックスがない場合、テーブル全体をスキャンして条件に一致する行を見つける。
  2. フィルタリング:

    • 条件に一致する行が見つかった後、その行をフィルタリングして保持する。このステップでは、age > 30という条件が適用される。
  3. プロジェクション:

    • フィルタリングされた行から必要な列(nameage)を選択する。
  4. 結果の生成:

    • 選択された列の結果セットを生成し、クライアントに返す。

実行エンジン(Execution Engine)

実行計画は最終的に実行エンジンによって処理される。 実行エンジンは、クエリオプティマイザが生成した実行計画を受け取り、実際にクエリを実行して結果を生成する。

1. 実行計画の受け取り:

  • 実行エンジンは、クエリオプティマイザから最適化された実行計画を受け取る。
  • 実行計画には、クエリの実行方法や各ステップの詳細が含まれる。

2. 実行計画の実行:

  • 実行エンジンは、受け取った実行計画に従ってクエリを実行する。
  • 各ステップを順番に処理し、データの取得、フィルタリング、結合、集計などの操作を行う。

3. 操作の例:

  • テーブルスキャンまたはインデックススキャン: テーブル全体をスキャンするか、インデックスを使用して効率的に行を検索する。
  • フィルタリング: 条件に一致する行をフィルタリングする。
  • プロジェクション: 必要な列のみを選択する。
  • 結合(JOIN): 複数のテーブルからデータを結合する。
  • 集計(AGGREGATION): 集計関数を使用してデータを集計する。

4. 結果の生成:

  • 実行エンジンは、実行計画に基づいて処理した結果を生成し、クライアントに返す。
  • 結果は、ユーザーが要求した形式で返される。

実行エンジンの役割

  • 効率的な処理: 実行エンジンは、実行計画に従って効率的にクエリを処理し、パフォーマンスを最大化する。
  • リソース管理: 実行エンジンは、必要なリソース(CPU、メモリ、I/O)を管理し、最適な形でクエリを実行する。
  • エラー処理: 実行中に発生するエラーや例外を適切に処理し、クライアントにフィードバックを提供する。

ストレージエンジンの主要機関

  1. トランザクションマネージャ(Transaction Manager):

  2. ロックマネージャ(Lock Manager):

    • 役割: データの一貫性を保つために、データアクセスに対するロックを管理する。
    • 機能:
      • ロック取得と解放: データへの同時アクセスを制御するために、ロックの取得と解放を行う。
      • デッドロック検出と回避: デッドロック(相互にロックを待つ状態)を検出し、回避するための処理を行う。
  3. アクセスメソッド(Access Methods):

    • 役割: データの格納と検索のための方法を提供する。
    • 機能:
      • インデックス: データの検索を高速化するためのインデックスを管理する。
      • データ構造: B-tree、Hash、R-treeなどのデータ構造を使用して、効率的なデータアクセスを提供する。
  4. バッファマネージャ(Buffer Manager):

    • 役割: データベースとストレージ(ディスク)との間のデータの読み書きを管理する。
    • 機能:
      • バッファプール: メモリ内にデータのキャッシュを保持し、ディスクI/Oの頻度を減らすことでパフォーマンスを向上する。
      • ページ管理: データページの読み込みと書き込みを効率的に管理する。
  5. リカバリマネージャ(Recovery Manager):

    • 役割: 障害発生時にデータベースを一貫した状態に回復するための機能を提供する。
    • 機能:
      • ログ管理: トランザクションログを使用して、障害発生時のリカバリを実行する。
      • チェックポイント: 定期的にデータベースの状態を保存し、リカバリ時に必要なログの量を最小限に抑える。

各機関の詳細

トランザクションマネージャ

ロックマネージャ

  • 排他ロック(Exclusive Lock): 書き込みアクセスのためのロック。他のトランザクションからの読み取りおよび書き込みアクセスを防ぐ。
  • 共有ロック(Shared Lock): 読み取りアクセスのためのロック。他のトランザクションからの読み取りは許可されますが、書き込みは許可されません。

アクセスメソッド

  • B-tree: 順序付きデータの格納と検索に適したバランスツリー構造。
  • ハッシュ: 一定のキーを持つデータの高速検索を可能にするハッシュテーブル。
  • R-tree: 空間データの格納と検索に適したツリー構造。

バッファマネージャ

  • バッファヒット率: メモリ内のデータページにアクセスできる割合。高いバッファヒット率はパフォーマンスの向上に寄与。
  • スワップアウト: メモリからディスクにデータページを移動するプロセス。

リカバリマネージャ

  • ログファイル: トランザクションのすべての変更が記録されるファイル。
  • チェックポイント: データベースの一貫性を保つために、定期的にデータベースの状態をディスクに保存します。

バッファマネージャ(Buffer Manager)

主な役割

  • データページのキャッシュ: バッファマネージャは、データベース内のデータページをメモリにキャッシュする。これにより、頻繁にアクセスされるデータをメモリ内に保持し、ディスクI/Oの頻度を減らすことでパフォーマンスを向上させる。

具体的な機能

  1. バッファプール管理:

    • バッファプールは、メモリ内に配置されたデータページのキャッシュ領域。バッファマネージャは、データベースから読み込まれるデータページをこのバッファプールに格納する。
  2. ページの読み込みと書き込み:

    • 読み込み(Read): クエリがデータを要求したとき、バッファマネージャはまずそのデータがバッファプールに存在するかを確認する(バッファヒット)。存在しない場合(バッファミス)、ディスクからデータページを読み込み、バッファプールにキャッシュする。
    • 書き込み(Write): データが更新されると、バッファマネージャは更新されたデータページをメモリ内のバッファプールに保持し、後でディスクに書き込む(遅延書き込み)ことで効率を向上させる。
  3. ページ置換アルゴリズム:

    • バッファプールの容量が限られているため、新しいデータページを読み込むために、使用頻度の低いページを置き換える必要がある。バッファマネージャは、LRU(Least Recently Used)やMRU(Most Recently Used)などのページ置換アルゴリズムを使用して、どのページを置き換えるかを決定する。
  4. バッファヒット率の最適化:

    • バッファマネージャは、バッファヒット率(メモリ内のデータページにアクセスできる割合)を最適化するために、頻繁にアクセスされるデータをバッファプールに保持する。高いバッファヒット率は、ディスクI/Oの削減につながり、クエリの応答時間を短縮する。

トランザクションマネージャの働きを具体例を通じて説明する。トランザクションマネージャは、データベースにおけるトランザクションの一貫性、原子性、分離性、耐久性(ACID特性)を保証する。以下に、トランザクションの典型的な流れを示す。

具体例:銀行口座間の振り込み

Aさんの銀行口座からBさんの銀行口座へ100ドルを振り込むというトランザクション

BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 'A';
UPDATE accounts SET balance = balance + 100 WHERE account_id = 'B';
COMMIT;

このトランザクションは以下のステップで進行する

1. トランザクションの開始(BEGIN TRANSACTION)

2. ステップ1: 更新操作1

  • SQL文:UPDATE accounts SET balance = balance - 100 WHERE account_id = 'A';
  • トランザクションマネージャは、accountsテーブルのaccount_id = 'A'に対して排他ロックを取得する。
  • バッファマネージャが必要なデータページをメモリに読み込み、balanceを100ドル減少させる。
  • 変更がトランザクションログに記録される。

3. ステップ2: 更新操作2

  • SQL文:UPDATE accounts SET balance = balance + 100 WHERE account_id = 'B';
  • トランザクションマネージャは、accountsテーブルのaccount_id = 'B'に対して排他ロックを取得する。
  • バッファマネージャが必要なデータページをメモリに読み込み、balanceを100ドル増加させる。
  • 変更がトランザクションログに記録される。

4. トランザクションのコミット(COMMIT)

5. エラーハンドリングとロールバック

トランザクションマネージャの主な役割

  1. トランザクションの開始と終了の管理

  2. ACID特性の保証

    • Atomicity(原子性): トランザクション内のすべての操作が完全に実行されるか、全く実行されないかを保証する。
    • Consistency(一貫性): トランザクションが完了すると、データベースは一貫した状態にあることを保証する。
    • Isolation(分離性): 同時実行するトランザクションが互いに影響を与えないようにする。
    • Durability(耐久性): トランザクションがコミットされた後、その結果が永続的に保存されることを保証する。
  3. ロックの管理

    • トランザクション間の競合を避けるために、適切なロック(共有ロックや排他ロック)を管理する。
  4. トランザクションログの管理

ロックマネージャの役割と働き

排他ロックや共有ロックの管理はロックマネージャが担当している。ロックマネージャは、トランザクション間でデータの整合性を保つために必要なロックの取得と解放を管理し、同時実行制御を行う。

1. ロックの種類

  • 排他ロック(Exclusive Lock, X Lock):
    • データの書き込み(更新)操作に必要なロック。他のトランザクションからの読み取りおよび書き込みアクセスを防ぐ。
  • 共有ロック(Shared Lock, S Lock):
    • データの読み取り操作に必要なロック。他のトランザクションからの読み取りは許可されるが、書き込みは許可されない。

2. ロックの取得と解放

  • ロックの取得:
    • トランザクションがデータにアクセスする際、ロックマネージャはそのデータに対して適切なロックを取得する。
    • 例えば、トランザクションがデータを更新しようとする場合、ロックマネージャは排他ロックを取得する。
  • ロックの解放:

3. デッドロックの検出と解決

ロックマネージャは定期的にトランザクションの状態を監視し、デッドロックが発生していないかをチェックする。 - デッドロック解決: - デッドロックが検出された場合、ロックマネージャはデッドロックを解消するために一つ以上のトランザクションロールバックする。

4. ロックの粒度(Lock Granularity)

ロックの粒度(Lock Granularity)は、データベースシステムにおけるロックの適用範囲を指す概念で、ロックを取得する単位がどれだけ細かいかを示す。ロックの粒度は、データベースのパフォーマンスと同時実行性に大きな影響を与える。

ロックの粒度の基本概念

ロックの粒度が細かいほど、ロックの適用範囲は狭くなり、データベース内の特定のデータ項目(例えば、行やレコード)にロックがかかる。 一方、ロックの粒度が粗いほど、ロックの適用範囲は広くなり、データベースの大きな範囲(例えば、ページやテーブル全体)にロックがかかる。

  • 細かい粒度のロック(Fine-Grained Locking): より高い同時実行性を提供するが、ロック管理のオーバーヘッドが増加する。
  • 粗い粒度のロック(Coarse-Grained Locking): ロック管理のオーバーヘッドは減少するが、同時実行性が低下する。

代表的なロックの粒度

  1. データベース全体のロック

    • 最も粗い粒度のロック。
    • 全体をロックすることで、データベース全体に対する一貫性を保つ。
    • 同時実行性は非常に低くなる。
  2. テーブルロック

    • テーブル全体をロックする。
    • テーブルレベルでの整合性を保つために使用される。
    • 同時実行性はデータベースロックよりも高いが、依然として低い。
  3. ページロック

    • データベース内のページ(通常は物理ディスクブロック)をロックする。
    • ページ単位でのアクセス制御が可能。
    • 同時実行性はテーブルロックよりも高いが、ロック管理のオーバーヘッドが増加する。
  4. 行ロック(レコードロック)

    • 個々の行またはレコードをロックする。
    • 非常に細かい粒度のロックで、高い同時実行性を提供。
    • ロック管理のオーバーヘッドが最も高い。
  5. フィールドロック

    • 特定のフィールド(列)をロックする。
    • 最も細かい粒度のロックで、非常に高い同時実行性を提供。
    • ロック管理のオーバーヘッドが非常に高くなるため、実際にはあまり使用されない。

具体例:排他ロックの取得と解放

前述のトランザクションの具体例を用いて、ロックマネージャの動作を説明する。

BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 'A';
UPDATE accounts SET balance = balance + 100 WHERE account_id = 'B';
COMMIT;

1. トランザクションの開始

2. 更新操作1

  • SQL: UPDATE accounts SET balance = balance - 100 WHERE account_id = 'A';
  • ロックの取得:
    • ロックマネージャがaccount_id = 'A'のレコードに対して排他ロックを取得する。
    • 他のトランザクションはこのレコードに対して読み取りおよび書き込みができなくなる。
  • データの更新:
    • バッファマネージャがデータページをメモリに読み込み、balanceを100ドル減少させる。
    • 変更がトランザクションログに記録される。

3. 更新操作2

  • SQL: UPDATE accounts SET balance = balance + 100 WHERE account_id = 'B';
  • ロックの取得:
    • ロックマネージャがaccount_id = 'B'のレコードに対して排他ロックを取得する。
    • 他のトランザクションはこのレコードに対して読み取りおよび書き込みができなくなる。
  • データの更新:
    • バッファマネージャがデータページをメモリに読み込み、balanceを100ドル増加させる。
    • 変更がトランザクションログに記録される。

4. トランザクションのコミット

ロックマネージャとトランザクションログの関係

  1. ロックマネージャ:

    • 主なデータ構造:
      • ロックテーブル: 現在取得されているロックの状態を保持する。各エントリは、どのトランザクションがどのリソースに対してどの種類のロックを保持しているかを示す。
      • ロックキュー: ロック待ちのトランザクションを管理する。特定のリソースに対するロックを待っているトランザクションがキューに追加される。
    • 主な役割:
      • ロックの取得と解放の管理。
      • 同時実行制御を通じてデータの一貫性を維持。
      • デッドロックの検出と解決。
  2. トランザクションログ:

ロックマネージャの詳細な動作

1. ロックの取得:

  • トランザクションが特定のデータ項目にアクセスしようとする際、ロックマネージャにロックの取得を要求する。
  • ロックマネージャはロックテーブルを参照して、要求されたデータ項目に対して既に他のロックが取得されているかを確認する。
  • 要求が競合しない場合、ロックマネージャはロックを付与し、ロックテーブルを更新する。
  • 要求が競合する場合、トランザクションはロックキューに追加され、ロックが解放されるのを待つ。

2. ロックの解放:

3. デッドロックの検出と解決:

トランザクションログの使用

一方、トランザクションログは次のように使用される。

1. トランザクションの開始と記録:

2. 操作の記録:

  • 各操作(挿入、更新、削除)が実行されるたびに、その操作内容を示すログレコードが追加される。これには、操作前後のデータの状態も含まれる。

3. コミットとロールバック:

4. 障害発生時のリカバリ:

トランザクションマネージャの役割

ロックマネージャの役割

連動した同時実行制御の具体例

1. トランザクションの開始

トランザクションマネージャがトランザクションを開始し、トランザクションログにその開始を記録する。

BEGIN TRANSACTION;

2. データの操作

トランザクションがデータを操作する際に、ロックマネージャが関与する。

UPDATE accounts SET balance = balance - 100 WHERE account_id = 'A';

3. データの操作(続き)

UPDATE accounts SET balance = balance + 100 WHERE account_id = 'B';

4. トランザクションのコミット

トランザクションが正常に終了すると、トランザクションマネージャがコミットを処理し、トランザクションログにコミットの記録を追加する。

COMMIT;
  • ロックの解放:
    • ロックマネージャは、account_id = 'A'およびaccount_id = 'B'の排他ロックを解放する。これにより、他のトランザクションがこれらのデータにアクセスできるようになる。
  • データの永続化:

デッドロックの処理

デッドロックが発生した場合、ロックマネージャがデッドロックを検出し、解消するためにトランザクションロールバックする。

デッドロックの例

トランザクション1:

UPDATE accounts SET balance = balance - 100 WHERE account_id = 'A'; -- 排他ロック取得
-- 次に 'B' のロックを待つ

トランザクション2:

UPDATE accounts SET balance = balance - 50 WHERE account_id = 'B'; -- 排他ロック取得
-- 次に 'A' のロックを待つ

この状態でデッドロックが発生すると、ロックマネージャはこれを検出し、どちらかのトランザクションロールバックする。

排他ロックの動作の具体例

具体的には、あるトランザクションが排他ロックを取得した場合、そのトランザクションがロックを保持している間は、他のトランザクションが同じデータに対して読み書きを行うことができなくなる。これにより、データの整合性と一貫性が保たれる。

1. トランザクションAの開始

BEGIN TRANSACTION;

2. トランザクションAがレコードに排他ロックを取得

UPDATE accounts SET balance = balance - 100 WHERE account_id = 'A';

3. トランザクションBが同じレコードにアクセスを試みる

SELECT balance FROM accounts WHERE account_id = 'A';

4. トランザクションAのコミットとロックの解放

COMMIT;

5. トランザクションBのアクセス

排他ロックの重要性

  • データの整合性: 排他ロックは、データの整合性を維持するために不可欠。例えば、データの更新中に他のトランザクションが同じデータを読み書きすることを防ぐ。
  • 一貫性の保証: トランザクションの実行中にデータが他のトランザクションによって変更されることを防ぐことで、一貫性を保証する。
  • 競合の回避: 複数のトランザクションが同時に同じデータにアクセスしようとする際の競合を回避する。

まとめ

排他ロックは、トランザクションがデータの更新を行う際に、他のトランザクションからの干渉を防ぐために使用される。これにより、データの整合性と一貫性が確保される。トランザクションが排他ロックを保持している間、他のトランザクションはそのデータに対する読み書きを行うことができない。これは特にデータの更新操作において重要な機能であり、データベースシステムの信頼性を維持するために必要である。