忍者ブログ
IT関係の小作人労働の日々の日記です。 最近データベースが好きです。 インフラ構築、DB構築、アプリケーション開発・・・何でも屋です。 何でもできそうで、何にもできない。

【データベース内部構造】SQLはどう処理される?クエリ実行の内部プロセスと最適化の流れ

SQL文を発行した際、データベース(RDBMS)内部ではどのような処理が行われているのだろうか。
SQLは「どのようなデータを取得・更新したいか」という宣言的な記述を行う言語であり、「具体的にどのようにデータを取得するか」という手順はRDBMS内部の処理エンジンが自動的に判断している。

今回は、SQLが投入されてから結果が返されるまでの内部ステップと、効率的な実行処理を行うための「クエリ最適化(Query Optimization)」の流れを整理した。


1. SQL処理の5つのステップ

一般的なRDBMSにおいて、SQL文は主に以下の5つの段階を経て実行される。

  • ① Parsing(構文解析)
    送られてきたSQLの文法的な正しさ(シンタックスチェック)を解釈・検証する。
    SQL実行後の最初のプロセスとして、Parser(構文解析器)がSQLのロジカルな構造を関係演算を表す「Query Tree(クエリツリー)」へ変換する。
  • ② Query Validation(クエリ検証・意味解析)
    SQL内に指定されたテーブルや列(カラム)がデータベース上に実際に存在するか、アクセス権限があるかなどの「意味的妥当性」を検証する。
  • ③ Optimization(最適化 / クエリ最適化)
    関係演算を表す Query Tree を、実際のアクセス手順である「物理的な実行プラン(Execution Plan)」へ変換する。
    通常、データ取得の手順には複数のバリエーション(多くのプラン)が存在するため、その中で最も効率が良い(低コストな)実行プランを選択する。
  • ④ Plan Compile(プランコンパイル)
    クエリオプティマイザ(Query Optimizer)によって選択された最適な計画を、実行エンジンが直接処理できるマシンコードや内部コマンド形式へコンパイル・変換する。
  • ⑤ Execution(実行)
    Query 実行エンジン(Execution Engine)が物理プランに従ってストレージやキャッシュメモリからデータを読み書きし、最終的な結果をクライアントへ返す。

2. クエリ最適化(Query Optimization)の役割

SQLの処理工程の中で、パフォーマンスを決定づける最も重要なコンポーネントが「クエリオプティマイザ(Query Optimizer)」である。

【クエリ最適化の仕組みとポイント】

■ 物理プランの大量生成
1つのSQLに対して、「フルスキャンをするか / インデックスを使うか」「テーブル結合の順序をどうするか」「ハッシュ結合かループ結合か」など、多数の物理プラン候補が生成される。

■ コスト評価(Cost-Based Optimization)
オプティマイザは統計情報(データ件数、インデックスのカーディナリティ、データ配置など)を参照し、推定実行時間やI/Oコストなどの「いろいろな値」から最適なプランを算出・算出決定する。

■ 実行エンジンへの受け渡し
最小コストと判定された計画(最適プラン)のみが選択され、Query 実行エンジンに渡されて高速な処理が実現される。

3. まとめ

SQLは「データ取得の結果」を指示するだけで、裏側ではParser(構文解析器)による Query Tree の生成と、Query Optimizer(最適化エンジン)による物理実行プランの決定という高度な処理が自動的に行われている。

SQLパフォーマンスチューニングにおいては、オプティマイザが意図通りの最適な実行プランを選択できるよう、「最新の統計情報(Statistics)の維持」や「適切なインデックス設計」を行うことが重要となる。

【SQL実行プロセスから見るチューニングのポイント】
✔ プレースホルダ(バインド変数)を使い、Parsing・Plan Compileの再利用(ハードパース削減)を狙えているか?
✔ テーブル統計情報は最新に更新されているか?(古い統計情報だとオプティマイザが間違ったプランを選ぶ)
✔ EXPLAIN / AUTOTRACE 等で最適化された物理実行プランを分析しているか?
PR

【データベース】データの信頼性を守る命綱!トランザクションの「ACID特性」とは

データベース(RDBMS)を扱う上で、絶対に避けて通れない最重要概念が「トランザクション(Transaction)」である。
銀行の口座振込やECサイトの注文処理など、複数のデータ更新を「1つの不可分な処理単位」として扱う仕組みを指す。

そして、このトランザクション処理が安全かつ正確に行われるために、RDBMSが保証すべき4つの性質の頭文字をとったものが「ACID(アシッド)特性」だ。
今回は、システム開発やデータベース設計の基本となるACID特性をわかりやすく整理した。


1. ACID特性の4つの要素

データベースのデータ破壊を防ぎ、整合性を完璧に保つためにRDBMSが備えるべき4つの原則は以下の通りである。

  • A:Atomicity(原子性 / All or Nothing)
    トランザクションに含まれる一連のデータ操作は、「すべて完全に実行される(Commit)」か「まったく実行されなかった状態に戻る(Rollback)」かのどちらか一方にしかならない性質。
    途中でエラーやシステム障害が発生した場合、中途半端に一部だけが更新された状態が残ることは絶対にない。
  • C:Consistency(一貫性 / 整合性)
    トランザクションの開始前と終了後で、データベースの整合性制約(主キー制約、外部キー制約、NOT NULL制約など)が常に満たされている性質。
    ルールに違反するような不正なデータ変更が行われそうになった場合、トランザクション全体が自動的に取り消される。
  • I:Isolation(独立性 / 隔離性)
    複数のトランザクションが同時に並行実行されていても、互いの処理過程が干渉・影響し合わない性質。
    他のトランザクションからは「まだ確定していない途中の状態」が見えず、あたかも1つずつ順番に実行(直列化)されたかのような結果が保証される。
  • D:Durability(永続性 / 堅牢性)
    トランザクションが一度確定(Commit)または取消(Rollback)した時点で、その結果は永続的に記憶され、絶対に失われない性質。
    確定直後にサーバーの電源が落ちたりシステム障害が発生したりしても、REDOログ等の仕組みによってデータは確実に復元される。

2. 具体例:銀行の口座振込で考えるACID

「Aさんの口座からBさんの口座へ1万円を振り込む」というトランザクション(①A口座から1万円引く ➔ ②B口座に1万円足す)を例に挙げると、ACID特性の重要性がより明確になる。

【振込処理におけるACIDの役割】
・原子性: ①のあと回線が切れても、B口座に足されずにA口座の1万円だけ消える(消滅する)事故を防ぐ(ロールバックされる)。
・一貫性: 残高がマイナス不可の制約がある場合、残高不足なら振込処理自体をエラーにして弾く。
・独立性: Aさんが振込処理を行っている最中に、CさんがAさんの残高を参照しても「振込途中の確定していない残高」は見えない。
・永続性: 「振込完了」の画面が出た直後に銀行のDBサーバーが停電しても、1万円の移動記録は消えずに残る。

3. まとめ

Webアプリケーションや基幹システムにおいて、データの辻褄が合わなくなるバグを防いでいるのは、RDBMSが提供するこのACID特性のおかげである。

システム設計時には、特に「隔離性(Isolation)レベルの設定(Dirty ReadやPhantom Readの防犯)」や「デッドロックの防止」など、ACID特性を意識したトランザクション設計を行うことが求められる。

【トランザクション設計のチェック項目】
✔ 複数の更新クエリが「1つのトランザクション」としてコミット/ロールバック制御されているか?(原子性)
✔ アプリ側の処理異常時に確実に ROLLBACK が発行される構造になっているか?(一貫性)
✔ 同時実行時のロック範囲や分離レベル(Read Committed等)が適切に設計されているか?(独立性)

【データベースの知識】現代のRDBの原点!コッドの12のルール(Codd's 12 Rules)とは

リレーショナルデータベース(RDB)の基礎理論を考案した Edgar F. Codd(エドガー・F・コッド)博士が1985年に提唱した「コッドの12のルール(Codd's 12 Rules)」。
「真のリレーショナルデータベース管理システム(RDBMS)とはどうあるべきか」を定義した指針であり、データベースの運用や基本設計、情報処理試験の理論問題においても極めて重要な概念となっている。

今回は、データベースを正しく管理・運用するためにRDBMSが備えるべき12の原則をわかりやすく整理した。


1. コッドの12のルール(RDBMSの要件定義)

コッド博士は、システムが独自の拡張や物理的な制約に頼らず、純粋に「リレーショナルモデル」としてデータの一貫性・独立性を保つために以下の12原則(および原則0である「完全なリレーショナル機能による管理」)を定めた。

  • ① 情報の表現原則(Information Rule)
    テーブル名や列名、制約といったメタデータを含め、データベース内のすべての情報は「表(リレーション)のセルに格納された値」として一元的に記録されなければならない。
  • ② アクセスの保証(Guaranteed Access Rule)
    すべてのデータ要素は、「テーブル名」「主キー(Primary Key)の値」「列(ドメイン)名」の組み合わせによって論理的に一意指定してアクセス可能でなければならない(物理アドレスやポインタの指定は不可)。
  • ③ NULLの統一的扱い(Systematic Treatment of Null Values)
    「値が存在しない(未知)」「適用不能」を表す NULL は、データ型に関わらず、システム全体で統一かつ体系的な方法で処理されなければならない。
  • ④ オンライン・アクティブ・カタログ(Dynamic Online Catalog)
    データベース構造を保持するカタログ(データディクショナリ)自体も通常のデータと同じ表形式で表現され、通常のSQL等のデータアクセス言語を使ってオンラインで照会できなければならない。
  • ⑤ 包括的なデータ言語の原則(Comprehensive Data Sublanguage Rule)
    データの定義(DDL)、ビューの定義、データの操作(DML)、整合性制約、トランザクション制御(COMMIT/ROLLBACK)などが、単一の明確に定義された言語(SQLなど)で完結して実現できなければならない。
  • ⑥ ビューの更新原則(View Updating Rule)
    理論的に更新可能なビューに対して更新(INSERT / UPDATE / DELETE)が発行された場合、RDBMSがそれを検知し、裏側にある実表(基底テーブル)に対して正しく更新処理を反映しなければならない。
  • ⑦ 高水準の挿入・更新・削除(High-Level Insert, Update, and Delete)
    データ操作言語は、一度の命令(クエリ)で1行ずつ処理するのではなく、複数の行(タプル/集合)をひと括りで集合処理できなければならない。
  • ⑧ 物理的データ独立性(Physical Data Independence)
    データの記憶形式やアクセスパス(インデックス構造やファイルの配置場所など)といった物理的な変更を行っても、アプリケーションプログラムのコードに影響を与えてはならない。
  • ⑨ 論理的データ独立性(Logical Data Independence)
    テーブルの分割・結合など、データベースの論理構造を変更した場合でも、既存のアプリケーションプログラムへの影響を最小限に抑えられなければならない(ビュー等の活用)。
  • ⑩ 整合性制約の独立性(Integrity Independence)
    主キー制約や外部キー制約などのデータ整合性制約は、アプリ側のプログラム内に記述するのではなく、RDBMS内のカタログに直接定義・保持されなければならない。制約が変更されてもアプリ側に影響を与えない。
  • ⑪ 分散の独立性(Distribution Independence)
    データベースが複数のサーバやネットワーク上に分散配置されたとしても、アプリケーション側からはあたかも単一のローカルデータベースを操作しているように見えなければならない(位置の透過性)。
  • ⑫ 規約無効化の否定(Non-Subversion Rule)
    RDBMSに低水準(レコード単位など)のインターフェースが存在する場合でも、その経路を使ってRDBMSに定義された整合性制約やセキュリティ規約を回避・破壊できてはならない。

2. まとめ:現代RDBMSにおける位置づけ

コッドの12のルールは、SQLデータベースの理想型を示した厳格な基準である。現実の主要なRDBMS(Oracle、PostgreSQL、MySQL、SQL Serverなど)であっても、これら12のルールを完全(100%)に満たしている製品は少ない。

しかし、「物理データからの独立」「集合操作」「整合性制約のRDBMS側での一元管理」といった根幹のコンセプトは、現代のデータベース設計やアプリケーション開発においても変わらない基礎知識として息づいている。

【データベース設計・運用のチェック項目】
✔ アプリケーション側に整合性チェックのロジックを抱え込みすぎていないか?(ルール10)
✔ 物理的なインデックス追加・変更がアプリ側に波及しない構造になっているか?(ルール8)
✔ NULLの判定ロジックがシステム全体で統一されているか?(ルール3)

【データベース】実行計画の要!「オプティマイザー」の役割と2つのアプローチ

リレーショナルデータベース(RDB)に対してSQL文を発行する際、私たちは「どのようなデータを満たすか(WHAT)」だけを指定し、「どのようにデータを取得するか(HOW)」の手順は記述しない。
この「どのようにデータを取得するか」という具体的なアクセス経路(実行計画)を裏で自動的に導き出しているのが、DBMSの心臓部である「オプティマイザー(最適化エンジン)」である。


1. オプティマイザーとは

データベースアクセスを最適化する仕組み、およびそのシステムをオプティマイザーと呼ぶ。

オプティマイザーは、ユーザーから送られてきたSQL文を構文解析(パース)し、テーブルの構造やインデックスの有無を分析する。その後、内部で複数の実行計画(プラン)を組み立て、その中から最も効率が良いと判断したアクセス経路を検出して実行に移す役割を担っている。


2. オプティマイザーの2つの種類

SQL文の実行計画を選択・決定する手法には、歴史的な経緯やアプローチの違いにより、大きく分けて次の2種類が存在する。

(1)ルールベース・オプティマイザー(RBO)

ルールベースのオプティマイザーは、データの量や状態に関わらず、あらかじめシステムに組み込まれた固定の優先順位(ルール)に則って、機械的に実行計画を決定する。

一般的には、以下のようなアクセス・パス(経路)の優先順位が定義されており、より上位の条件にマッチするインデックス等があれば、それを優先的に選択する。

  • (ア)ユニーク・キーまたは主キーによる単一行アクセス(最優先)
  • (イ)インデックス付きのクラスター・キー
  • (ウ)複合インデックス(複数カラムを組み合わせたインデックス)
  • (エ)単一カラム・インデックス
  • (オ)ソート/マージ結合
  • (カ)インデックス付きカラムの MAX または MIN 関数
  • (キ)インデックス付きカラムの ORDER BY
  • (ク)テーブル全体のスキャン(フルスキャン)(最も優先順位が低い)

(2)コストベース・オプティマイザー(CBO)

現在の主要なDBMS(Oracle、PostgreSQL、MySQL、SQL Serverなど)で主流となっているのが、このコストベースである。
あらかじめデータベース内に収集・蓄積されている、テーブルのレコード数、カラムの平均長、インデックスの深さ、データの分布度といった「統計情報」を基にする。

オプティマイザーは内部で複数の実行計画をシミュレーションし、それぞれのパターンで発生する「コスト」を計算して、最もコストが低いプランを採用する。

※ここでいう「コスト」とは、SQL文を実行するために必要と予測される、CPU処理時間やディスクI/O(読み書き)の経過時間に比例する見積値のことである。データ量が少なければフルスキャンを選び、データ量が多ければインデックスを選ぶ、といった柔軟な判断が可能となる。


3. まとめ

ルールベースは「構造」だけで判断するためデータ量の変化に弱く、現代の複雑なシステムでは不都合が生じやすい。そのため、現在のRDB運用においては、定期的に ANALYZE などのコマンドを実行して最新の「統計情報」を維持し、コストベース・オプティマイザーに正しい判断をさせることが、パフォーマンス管理における最大の鉄則となっている。

【オプティマイザー運用のチェック項目】
□ 意図しないフルスキャン(テーブル全体スキャン)が発生していないか?
□ 大規模なデータ更新(バッチ処理等)のあとに統計情報を更新しているか?
□ 期待通りのインデックスアクセスが選択されるよう、実行計画(EXPLAIN)を確認したか?

【データベースの知識】データベース運用で知っておくべき「更新可能なView」の8つの必須条件

SQLにおいて、複雑なクエリをカプセル化して擬似的なテーブルのように扱える「ビュー(View)」。
ビューは通常、データの参照(SELECT)に用いられるが、特定の条件をクリアしていれば、ビューに対して直接 INSERT、UPDATE、DELETE などの更新処理(DML)を行うことができる。

今回は、データベース設計や情報処理試験でも極めて重要となる、標準SQLにおける「更新可能なビュー」が満たすべき8つの厳格な条件を整理した。


1. 更新可能なビュー(Updatable View)の8大条件

DBMSがビューの向こう側にある実テーブル(基底テーブル)に対して、どのレコードのどの列を書き換えるべきかを一意に特定(マッピング)できる必要があるため、以下の条件が定められている。

  • ① 1つだけのテーブルから作られていること
    複数のテーブルを結合(JOIN)して作成されたビューは、原則として標準SQLでは直接更新できない。対象となる基底テーブルが単一である必要がある。
  • ② GROUP BY 句を使っていないこと
    データをグループ化してしまうと、ビュー上の1行が基底テーブルの複数行をまとめたものになってしまうため、どの行を更新すべきか特定できなくなる。
  • ③ HAVING 句を使っていないこと
    GROUP BY と同様に、グループ化された集計結果に対する絞り込みを行う句が含まれているビューは更新対象外となる。
  • ④ 集約関数(SUM, AVG, COUNT, MAX, MIN など)を使っていないこと
    計算・集計された結果の値を書き換えようとしても、基底テーブルの元のデータをどう変更すればよいか計算が成り立たないためである。
  • ⑤ 計算列を使っていないこと
    SELECT col1 + col2 AS total のような演算式によって算出された列(計算列)は、直接値を書き換えることができない。
  • ⑥ UNION、INTERSECT、EXCEPT を使っていないこと
    集合演算子を用いて複数のクエリ結果を統合・比較しているビューは、データの出所を一意にマッピングできないため更新できない。
  • ⑦ SELECT DISTINCT 句を使っていないこと
    重複行を排除(一意化)して表示されたビューは、実テーブルのどの行に対応しているかの追跡性が失われるため、更新が許可されない。
  • ⑧ ビューに含まれない基底テーブルのすべての列が、NULLを許可するか、デフォルト値が指定されていること
    ビュー経由で INSERT(行追加)を行う際、ビューに定義されていない列にはデータが渡らない。そのため、それらの列が NOT NULL(かつデフォルト値なし)の制約を持っていると、実テーブル側で制約エラーが発生して挿入できなくなる。

2. まとめ:実務でのアプローチ

上記のように、標準SQLでビューを更新可能にするためのハードルはかなり高い。実テーブルとビューの列が「1対1」できれいに対応しているシンプルなビューだけが、そのまま更新できる仕様になっている。

もし、これらの条件を満たさない複雑なビュー(複数テーブルの結合ビューなど)に対してどうしても更新処理を行いたい場合は、PostgreSQL等の主要なDBMSでサポートされているINSTEAD OF トリガーをビューに実装し、内部の更新ロジックを開発者が手動で記述するアプローチをとるのが一般的である。

【ビュー設計時のチェック項目】
□ 参照専用(読み取り専用)として利用するビューか?
□ アプリケーションからビューを介した DML 操作を想定しているか?
□ 条件を満たさない場合、トリガーによる代替ロジックの実装が必要か?
        
  • 1
  • 2
  • 3