MochiuWiki : SUSE, EC, PCB
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
MySQL - パフォーマンスチューニングのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
MySQL - パフォーマンスチューニング
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == MySQLのパフォーマンスチューニングは、データベースアプリケーションの応答速度とスループットを向上させるための重要なプロセスである。<br> <br> チューニングは、インデックス最適化、クエリの改善、InnoDB設定の調整、実行計画の分析など、複数の領域にわたる。<br> 適切なチューニングにより、同一ハードウェアでも数倍から数十倍のパフォーマンス向上が実現可能である。<br> <br> MySQL 8.0では、オプティマイザヒント、ヒストグラム統計、EXPLAIN ANALYZEなど、新たなチューニング機能が追加されている。<br> 一方で、クエリキャッシュはMySQL 8.0で完全に廃止されており、代替手段への移行が必要となる。<br> <br> 測定と分析を繰り返し、ボトルネックを特定してから対策を実施することが、効果的なチューニングの基本方針である。<br> <br> 関連する設定パラメータの詳細は [[MySQL - 設定ファイル]] を、クエリ構文の最適化は [[MySQL - SELECT]] を参照すること。<br> <br><br> == チューニングの基本方針 == パフォーマンスチューニングは、測定と分析に基づいた体系的なアプローチが必要である。<br> <br> ==== 測定と分析の重要性 ==== チューニングの第1歩は、現状のパフォーマンス測定である。<br> 測定なしに最適化を行うと、効果のない変更や逆効果の変更を実施するリスクが高まる。<br> <br> 主な測定対象として、クエリ実行時間、リソース使用状況、テーブル統計が挙げられる。<br> クエリ実行時間は、Slow Query Logで閾値を超えるクエリを記録し、Performance Schemaで全クエリの統計情報を収集する。<br> リソース使用状況は、CPU使用率、メモリ使用量、ディスクI/Oを監視し、<code>SHOW GLOBAL STATUS</code> で各種カウンタを確認する。<br> テーブル統計は、<code>SHOW TABLE STATUS</code> でテーブルサイズと行数を確認し、<code>INFORMATION_SCHEMA.TABLES</code> で詳細統計を取得する。<br> <br> ==== ワークロードの分類 ==== データベースのワークロードを分類し、最適化の方向性を決定する。<br> <br> <center> {| class="wikitable" |+ MySQLの環境別特性と最適化対策 ! 環境 / ワークロード !! 特性 !! 重要事項 !! 推奨対策 |- | OLTP (Online Transaction Processing) || 短時間の読み書きトランザクションが多数発生 || 同時実行性とレスポンスタイム || インデックス最適化、バッファプール設定 |- | OLAP (Online Analytical Processing) || 大規模なSELECT、集約、分析クエリが中心 || スループット || ソート用メモリ増加、REDOログサイズ増加 |- | 混合ワークロード || OLTPとOLAPが混在 || ワークロード特性の分離 || 読み取りレプリカの分離、パラメータの優先順位調整 |} </center> <br> ==== チューニングの優先順位 ==== 効果の大きい順にチューニングを実施する。<br> 各段階で測定を行い、効果を検証してから次の段階に進む。<br> <br> 推奨順序を以下に示す。<br> <br> # インデックスの最適化 #: 最も効果が大きく、リスクが低い。フルテーブルスキャンの削減が最優先である。 # クエリの最適化 #: 不適切なJOIN、サブクエリ、ソートを改善する。 #: EXPLAINで実行計画を確認する。 # InnoDB設定の調整 #: バッファプールサイズ、REDOログ、I/O設定を調整する。 #: サーバ全体のパフォーマンスに影響する。 # オプティマイザヒント #: 特定のクエリの実行計画を調整する。 #: インデックスとクエリ最適化で解決できない場合のみ使用する。 <br><br> == インデックスの最適化 == インデックスは、データベースパフォーマンスに最も影響を与える要素である。<br> <br> ==== インデックスの種類 ==== MySQLでは複数のインデックス形式がサポートされている。<br> <br> <center> {| class="wikitable" |+ MySQLインデックスタイプの比較 ! インデックスタイプ !! 対応エンジン !! 対応検索形式 !! 特性 |- | B-Tree || InnoDB、MyISAM || 等値検索、範囲検索、ORDER BY、GROUP BY || MySQLのデフォルトインデックス形式<br>リーフノードはデータポインタまたはPRIMARY KEYを保有 |- | ハッシュ || MEMORY || 等値検索のみ || 範囲検索、ORDER BY、GROUP BYには使用不可 |- | フルテキスト || InnoDB、MyISAM (MySQL 5.6以降) || 自然言語検索、ブール検索 || MATCH() AGAINST()構文で使用<br>VARCHAR、TEXT、CHARカラムに作成可能 |- | 空間インデックス || InnoDB (MySQL 8.0以降) || GIS検索 || GEOMETRYカラムに作成<br>地理情報システム検索に使用 |} </center> <br> <u>OLTP環境では、B-Treeインデックスが最も一般的に使用される。</u><br> <br> ==== 複合インデックスの設計 ==== 複数カラムにまたがるインデックスは、適切に設計することで効果が最大化される。<br> <br> <center> {| class="wikitable" |+ 複合インデックスの最左プレフィックスルール ! インデックス定義 !! 使用可能な検索 !! 使用不可な検索 |- | (A, B, C) || Aのみ、(A, B)、(A, B, C) || Bのみ、Cのみ、(B, C) |} </center> <br> <center> {| class="wikitable" |+ 複合インデックスのカラム配置順序 ! 配置ルール !! 説明 !! 具体例 |- | 等値条件を左に配置 || 等値条件 (=) を左に、範囲条件 (<, >, BETWEEN) を右に配置 || WHERE A = 10 AND B > 20 AND C = 30の場合、(A, B, C)ではCが使用されない。<br>一方、WHERE A = 10 AND B = 20 AND C > 30の場合、(A, B, C)が全て使用される。 |} </center> <br> <center> {| class="wikitable" |+ 複合インデックスの選択度設計 ! 設計項目 !! 説明 !! 具体例 |- | カーディナリティの高いカラムを左に配置 || 選択度の高いカラム (異なる値が多い) を左に配置すると、絞り込み効果が大きい || 性別 (2値) より顧客ID (数十万値) を左に配置する |} </center> <br> 設計例を以下に示す。<br> <br> <syntaxhighlight lang="mysql"> CREATE INDEX idx_order ON orders(customer_id, order_date, status); </syntaxhighlight> <br> このインデックスは、<br> <u>WHERE customer_id = 123</u>、<u>WHERE customer_id = 123 AND order_date >= '2026-01-01'</u>、 <u>WHERE customer_id = 123 AND order_date >= '2026-01-01' AND status = 'shipped'</u><br> に効果的である。<br> <br> 一方、<u>WHERE order_date >= '2026-01-01'</u>、<u>WHERE status = 'shipped'</u> には効果が薄い。(最左プレフィックスが使用されない)<br> <br> <u>複合インデックスは、単一カラムインデックスの組み合わせよりも効果的な場合が多い。</u><br> <br> ==== カバリングインデックス ==== カバリングインデックスは、SELECT句で必要な全カラムがインデックスに含まれている状態を指す。<br> <br> テーブル本体へのアクセスが不要となり、ディスクI/Oを大幅に削減できる。<br> ランダムアクセスからシーケンシャルアクセスに変換される。<br> <br> EXPLAINのExtraカラムに <u>Using index</u> と表示される。<br> <br> 設計例を以下に示す。<br> <br> <syntaxhighlight lang="mysql"> CREATE INDEX idx_covering ON users(last_name, first_name, email); </syntaxhighlight> <br> 以下のクエリがカバリングインデックスで実行される。<br> <br> <syntaxhighlight lang="mysql"> SELECT first_name, email FROM users WHERE last_name = 'Smith'; </syntaxhighlight> <br> InnoDBでは、全てのセカンダリインデックスに <code>PRIMARY KEY</code> が自動的に追加される。<br> 例えば、<u>INDEX(A, B)</u> は実際には <u>INDEX(A, B, PRIMARY_KEY)</u> として機能する。<br> <code>PRIMARY KEY</code> もカバリングインデックスに含めることができる。<br> <br> カバリングインデックスは、頻繁に実行されるクエリに対して効果が大きい。<br> <br> ==== プレフィックスインデックス ==== 長い文字列カラムに対して、先頭の一部のみをインデックス化する手法である。<br> <br> 構文例を以下に示す。<br> <br> <syntaxhighlight lang="mysql"> CREATE INDEX idx_prefix ON articles(title(20)); </syntaxhighlight> <br> この例では、<u>title</u> カラムの先頭20文字のみがインデックス化される。<br> <br> メリットとして、インデックスサイズが削減され、ディスク使用量とメモリ使用量が減少する。<br> インデックススキャンが高速化する。<br> <br> 制約として、ORDER BY / GROUP BYには使用できない。(プレフィックスのみでソート順が確定しないため)<br> カバリングインデックスには使用できない。(完全なカラム値がインデックスに含まれないため)<br> <br> 適切なプレフィックス長の決定例を以下に示す。<br> <br> <syntaxhighlight lang="mysql"> SELECT COUNT(DISTINCT LEFT(title, 10)) AS prefix_10, COUNT(DISTINCT LEFT(title, 20)) AS prefix_20, COUNT(DISTINCT LEFT(title, 30)) AS prefix_30, COUNT(DISTINCT title) AS full_column FROM articles; </syntaxhighlight> <br> 選択度 (異なる値の比率) が十分に高い最小のプレフィックス長を選択する。<br> VARCHAR(255) 以上、TEXT、BLOBカラムでの使用が推奨される。<br> <br> ==== インデックスの管理 ==== インデックスは定期的に見直し、不要なものは削除する。<br> <br> インデックスの確認は、以下のコマンドで実行する。<br> <br> <syntaxhighlight lang="mysql"> SHOW INDEX FROM <テーブル名>; </syntaxhighlight> <br> 未使用インデックスの検出は、以下のクエリで実行する。<br> <br> <syntaxhighlight lang="mysql"> SELECT * FROM sys.schema_unused_indexes WHERE object_schema = 'database_name'; </syntaxhighlight> <br> このビューは、Performance Schemaのイベントデータに基づいて未使用インデックスを検出する。<br> <br> インデックスの削除は、以下のコマンドで実行する。<br> <br> <syntaxhighlight lang="mysql"> ALTER TABLE <テーブル名> DROP INDEX <インデックス名>; </syntaxhighlight> <br> 削除前に不可視インデックス機能でテストすることを推奨する。<br> 削除後に再作成するとテーブルロックが発生する可能性があり、大規模テーブルでは再作成に長時間を要する。<br> <br> インデックスのメンテナンスとして、<code>OPTIMIZE TABLE</code> でインデックスの断片化を解消する。<br> InnoDBではテーブル全体の再構築が行われるため、大規模テーブルでは長時間を要する。<br> メンテナンスウィンドウで実行する。<br> <br> <code>ANALYZE TABLE</code> でインデックス統計を更新する。<br> オプティマイザが最新の統計情報を使用するために必要であり、データ分布が大きく変化した後に実行する。<br> <br> ==== 不可視インデックス (MySQL 8.0) ==== 不可視インデックスは、インデックス削除前の影響検証に使用する。<br> <br> インデックスを不可視化する構文を以下に示す。<br> <br> <syntaxhighlight lang="mysql"> ALTER TABLE <テーブル名> ALTER INDEX <インデックス名> INVISIBLE; </syntaxhighlight> <br> インデックスを可視化する構文を以下に示す。<br> <br> <syntaxhighlight lang="mysql"> ALTER TABLE <テーブル名> ALTER INDEX <インデックス名> VISIBLE; </syntaxhighlight> <br> オプティマイザがインデックスを無視するため、クエリの実行計画にインデックスが含まれなくなる。<br> パフォーマンスへの影響を検証可能である。<br> インデックスのメンテナンスは継続されるため、INSERT / UPDATE / DELETE 時にインデックスは更新される。<br> <br> また、いつでも可視化して即座に使用可能である。<br> <br> オプティマイザヒントで不可視インデックスを強制使用する構文を以下に示す。<br> <br> <syntaxhighlight lang="mysql"> SELECT /*+ USE_INDEX(<テーブル名> <インデックス名>) */ * FROM <テーブル名> WHERE ...; </syntaxhighlight> <br> セッション変数で不可視インデックスを有効化する構文を以下に示す。<br> <br> <syntaxhighlight lang="mysql"> SET SESSION optimizer_switch = 'use_invisible_indexes=on'; </syntaxhighlight> <br> 不可視インデックスは、本番環境でのインデックス削除のリスクを軽減する。<br> <br><br> == EXPLAIN による実行計画の分析 == <code>EXPLAIN</code> は、クエリの実行計画を表示し、パフォーマンス問題を特定する。<br> <br> ==== EXPLAINの基本 ==== 基本構文を以下に示す。<br> <br> <syntaxhighlight lang="mysql"> EXPLAIN SELECT * FROM users WHERE last_name = 'Smith'; </syntaxhighlight> <br> <code>EXPLAIN</code> の出力には複数のカラムが含まれる。<br> 下表に、主要なカラムの意味を示す。<br> <br> <center> {| class="wikitable" |+ EXPLAIN出力カラム ! カラム !! 説明 |- | id || SELECT識別子。数値が小さいほど外側のクエリ。同じidは同時実行される |- | select_type || SIMPLE (単純なSELECT、サブクエリなし)、PRIMARY (外側のクエリ)、SUBQUERY (サブクエリ)、DERIVED (派生テーブル、FROM句のサブクエリ)、UNION (UNION の2番目以降のSELECT) |- | table || 参照されるテーブル名。派生テーブルの場合は <derived2> のように表示 |- | type || アクセスタイプ。パフォーマンスに最も影響。後述の「typeカラムの最適化」を参照 |- | possible_keys || 使用可能なインデックス候補。NULLの場合はインデックスが存在しない |- | key || 実際に使用されるインデックス。NULLの場合はインデックスが使用されない |- | key_len || 使用されるインデックスの長さ (バイト単位)。複合インデックスでどこまで使用されているかを示す |- | ref || インデックスと比較されるカラムまたは定数。const (定数)、database.table.column (他テーブルのカラム) |- | rows || 読み込む推定行数。実際の行数とのズレが大きい場合は統計情報の更新が必要 |- | filtered || WHERE句で絞り込まれる行の割合 (%)。100%に近いほど効率的 |- | Extra || 追加情報。後述の「Extraカラムの注意点」を参照 |} </center> <br> JSON形式での出力は、以下の構文で実行する。<br> <br> <syntaxhighlight lang="mysql"> EXPLAIN FORMAT=JSON SELECT ...; </syntaxhighlight> <br> <u>JSON形式では、コストや実行時間の推定値など、より詳細な情報が表示される。</u><br> <br> ==== EXPLAIN ANALYZE (MySQL 8.0.18以降) ==== <code>EXPLAIN ANALYZE</code> は、実行計画と実際の実行統計を表示する。<br> <br> 構文を以下に示す。<br> <br> <syntaxhighlight lang="mysql"> EXPLAIN ANALYZE SELECT * FROM users WHERE last_name = 'Smith'; </syntaxhighlight> <br> 実際の実行時間 (ミリ秒単位) が各ステップに表示されるため、ボトルネックの特定が容易である。<br> <br> 実際の行数が表示され、<u>推定行数</u> と <u>実際の行数</u> を比較することができる。<br> もし、ズレが大きい場合はヒストグラム統計の作成を検討する。<br> <br> ループ回数として、ネステッドループの実行回数が表示される。<br> <br> クエリが実際に実行されるため、UPDATE / DELETE では実際にデータが変更される。<br> そのため、本番環境での使用は注意が必要である。<br> 実行時間が長いクエリでは完了まで待機するため、テスト環境での使用を推奨する。<br> <br> <code>EXPLAIN ANALYZE</code> は、オプティマイザの推定と実際の実行のギャップを可視化する。<br> <br> ==== type カラムの最適化 ==== typeカラムは、テーブルアクセスの方法を示し、パフォーマンスに直接影響する。<br> <br> 下表に、アクセスタイプの優劣 (上から順に高速) を示す。<br> <br> <center> {| class="wikitable" |+ アクセスタイプの優劣 ! type !! 説明 |- | system || システムテーブル (1行のみ) への参照。最速だが稀 |- | const || PRIMARY KEY または UNIQUE KEYでの等値検索。最大1行のみが該当。例: WHERE id = 123 (id がPRIMARY KEY) |- | eq_ref || JOINでPRIMARY KEY または UNIQUE KEYによる結合。各行に対して最大1行が結合される。例: JOIN ON t1.id = t2.id (id がPRIMARY KEY) |- | ref || 非ユニークなインデックスでの等値検索。複数行が該当する可能性がある。例: WHERE last_name = 'Smith' (last_name にインデックス) |- | range || インデックスを使用した範囲検索。BETWEEN、IN、<、>、<=、>= で使用。例: WHERE id BETWEEN 100 AND 200 |- | index || インデックス全体スキャン。テーブル全体スキャンより高速だが、大規模インデックスでは遅い。カバリングインデックスの場合に発生しやすい |- | ALL || フルテーブルスキャン。最も遅い。大規模テーブルでは避けるべき。インデックスが存在しないか、オプティマイザが使用を選択しなかった |} </center> <br> 最適化の目標として、<u>ALL</u> を <u>range</u> または <u>ref</u> 以上に改善する。<br> インデックスの追加または既存インデックスの利用を検討する。<br> <br> rangeをref以上に改善する場合は、範囲条件を等値条件に変更できる場合は変更する。<br> <u>ref</u> を <u>eq_ref</u> に改善する場合は、JOINカラムを <code>PRIMARY KEY</code> または <code>UNIQUE KEY</code> に変更する。<br> <br> typeがALLの場合は、<u>possible_keys</u> を確認してインデックス追加を検討する。<br> <br> ==== Extraカラムの注意点 ==== <u>Extra</u> カラムには、実行計画の追加情報が表示される。<br> <br> 下表に、パフォーマンス問題を示す値を示す。<br> <br> <center> {| class="wikitable" |+ パフォーマンス問題を示すExtra値 ! Extra !! 説明 !! 対策 |- | Using filesort || ディスク上でのソートが発生<br>ORDER BY句のカラムがインデックスに含まれていない。<br>メモリ内で完結しない場合はディスクI/Oが発生 || ORDER BYカラムをインデックスに含める<br><code>sort_buffer_size</code> を増加させる。 |- | Using temporary || 一時テーブルが作成される。<br>GROUP BY、DISTINCT、UNION、サブクエリで発生<br>メモリ内一時テーブルはディスク一時テーブルより高速 || GROUP BYカラムをインデックスに含める<br><code>tmp_table_size</code> と <code>max_heap_table_size</code> を増加させる。 |- | Using where || WHERE句でのフィルタリングが発生<br>インデックスで絞り込めなかった条件を後処理<br>必ずしも問題ではないが、行数が多い場合は最適化を検討 || インデックスの追加または改善 |} </center> <br> 下表に、パフォーマンス向上を示す値を示す。<br> <br> <center> {| class="wikitable" |+ パフォーマンス向上を示すExtra値 ! Extra !! 説明 |- | Using index || カバリングインデックスが使用される。<br>テーブル本体へのアクセスが不要<br>ディスクI/Oが大幅に削減 |- | Using index condition || Index Condition Pushdown (ICP) が使用される。<br>インデックススキャン時にWHERE条件の一部を評価<br>テーブル本体へのアクセス回数が減少 |} </center> <br> 下表に、その他の値を示す。<br> <br> <center> {| class="wikitable" |+ その他のExtra値 ! Extra !! 説明 |- | Using join buffer (Block Nested Loop) || JOINバッファが使用される。<br>結合カラムにインデックスがない場合に発生<br>MySQL 8.0.18以降はハッシュJOINが使用される |- | Using join buffer (hash join) || ハッシュJOINが使用される。(MySQL 8.0.18以降)<br>Block Nested Loopより高速 |- | Impossible WHERE || WHERE条件が常に偽<br><br>例: WHERE 1 = 0 |} </center> <br> <u>Using filesort</u> と <u>Using temporary</u> が同時に表示される場合は、最優先で最適化する。<br> <br><br> == クエリの最適化 == 適切なクエリ構造は、インデックス以上にパフォーマンスに影響する。<br> <br> ==== SELECT文の最適化 ==== 基本的なSELECT文の最適化手法を以下に示す。<br> <br> <code>SELECT *</code> を避け、必要なカラムのみを指定する。<br> 不要なカラムの転送がネットワーク帯域を消費し、カバリングインデックスが使用できなくなる可能性がある。<br> 例えば、<u>SELECT id, name, email FROM users</u> (必要なカラムのみ抽出) のように記述する。<br> <br> LIMITでスキャン行数を制限する。<br> 結果セットのサイズを制限することで、パフォーマンスが向上する。<br> 例えば、SELECT * FROM logs ORDER BY created_at DESC LIMIT 100 のように記述する。<br> <br> ただし、全行スキャン後にLIMITが適用されると効果が薄い。<br> <code>ORDER BY</code> カラムにインデックスがあれば効果的である。<br> <br> <code>DISTINCT</code> は、必要な場合のみ使用する。<br> これは、DISTINCTはソートまたはグループ化を伴うからである。<br> <br> インデックスがない場合は、Using temporaryが発生する。<br> GROUP BYで代替できる場合は、GROUP BYを使用する。<br> <br> WHERE句でカラムに関数を適用しない。<br> 例えば、<u>WHERE YEAR(created_at) = 2026</u> はインデックスが使用されない。<br> 代わりに、<u>WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01'</u> と記述すると、インデックスが使用される。<br> <br> 関数ベースインデックス (MySQL 8.0) を使用する場合は、以下のように記述する。<br> この場合、<u>WHERE YEAR(created_at) = 2026</u> でインデックスが使用される。<br> <br> <syntaxhighlight lang="mysql"> CREATE INDEX idx_year ON logs ((YEAR(created_at))); </syntaxhighlight> <br> ==== JOINの最適化 ==== JOINは、複数テーブルの結合時にパフォーマンスが低下しやすい。<br> <br> WHERE句で最も絞り込めるテーブルを駆動表にする。<br> オプティマイザが自動的に選択するが、適切でない場合もある。<br> STRAIGHT_JOIN または JOIN_ORDERヒントで順序を指定する。<br> <br> ON句のカラムにインデックスを設定する。<br> 駆動表のJOINカラムにはインデックス不要 (全行スキャンされるため)。<br> 被駆動表のJOINカラムには必ずインデックスを設定する。<br> 例えば、<u>SELECT * FROM orders o JOIN customers c ON o.customer_id = c.id</u> の場合、<u>customers.id (PRIMARY KEY)</u> にはインデックスが存在する。<br> <br> Batched Key Access (BKA) は、JOINバッファを使用してランダムアクセスを削減する。<br> デフォルトで無効であり、有効化するには以下のように設定する。<br> <br> <syntaxhighlight lang="mysql"> SET optimizer_switch = 'batched_key_access=on,mrr=on,mrr_cost_based=off'; </syntaxhighlight> <br> Multi-Range Read (MRR) の有効化が必要である。<br> ランダムアクセスが多いJOINで効果的である。<br> <br> ハッシュJOIN (MySQL 8.0.18以降) は、インデックスがないJOINで自動的に使用される。<br> Block Nested Loopより高速である。<br> <u>join_buffer_size</u> がハッシュテーブルのサイズに影響する。<br> EXPLAINのExtraに <u>Using join buffer (hash join)</u> と表示される。<br> <br> ==== サブクエリの最適化 ==== サブクエリは、適切に使用しないとパフォーマンスが低下する。<br> <br> 相関サブクエリは、外側のクエリの各行で実行される。<br> 例えば、以下のクエリでは、customer_id ごとにサブクエリが実行される。<br> <br> <syntaxhighlight lang="mysql"> SELECT * FROM orders o WHERE o.amount > (SELECT AVG(amount) FROM orders WHERE customer_id = o.customer_id); </syntaxhighlight> <br> 行数が多い場合は極めて遅い。<br> <br> JOINへの書き換えにより、サブクエリが1回のみ実行される。<br> <br> <syntaxhighlight lang="mysql"> SELECT o.* FROM orders o JOIN (SELECT customer_id, AVG(amount) AS avg_amount FROM orders GROUP BY customer_id) AS avg_orders ON o.customer_id = avg_orders.customer_id WHERE o.amount > avg_orders.avg_amount; </syntaxhighlight> <br> EXISTSの使用により、INよりEXISTSが効率的な場合がある。<br> <br> <syntaxhighlight lang="mysql"> SELECT * FROM customers c WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id); </syntaxhighlight> <br> EXISTSは最初の一致で評価を終了する。<br> INはサブクエリの全結果を評価する。<br> MySQL 8.0ではINも半結合最適化される。<br> <br> 派生テーブルの最適化 (MySQL 8.0) により、派生テーブルがマテリアライズされる。<br> MySQL 8.0では派生テーブルのマージまたはマテリアライゼーションが自動的に選択される。<br> <br> ==== ソートの最適化 ==== ORDER BYは、インデックスが使用されない場合にfilesortが発生する。<br> <br> ORDER BYカラムをインデックスに含めることで、EXPLAINのExtraに <code>Using filesort</code> が表示されない。<br> 例えば、CREATE INDEX idx_order_date ON orders(order_date) を作成し、SELECT * FROM orders ORDER BY order_date を実行する。<br> <br> 複合インデックスでのソートでは、ORDER BYカラムがインデックスの左から連続している必要がある。<br> 例えば、INDEX(A, B, C) は ORDER BY A, B または ORDER BY A, B, C に使用可能である。<br> ORDER BY B, C または ORDER BY A, C には使用できない。<br> WHERE句とORDER BY句の組み合わせでは、INDEX(status, order_date) は WHERE status = 'shipped' ORDER BY order_date に使用可能である。<br> <br> ソートバッファサイズとして、<code>sort_buffer_size</code> を増加する。<br> デフォルトは256KBである。<br> セッション単位で設定可能であり、SET SESSION sort_buffer_size = 1048576; (1MB) のように記述する。<br> メモリ内でソートが完結する場合はディスクI/Oが発生しない。<br> 大きすぎるとメモリ不足を引き起こす可能性がある。<br> <br> ソート方向として、昇順と降順の混在は最適化が困難である。<br> 例えば、ORDER BY A ASC, B DESC はインデックスが使用されにくい。<br> MySQL 8.0では降順インデックスがサポートされており、CREATE INDEX idx ON table(A ASC, B DESC) のように記述する。<br> <br> ==== GROUP BY / DISTINCT の最適化 ==== GROUP BYとDISTINCTは、一時テーブルまたはソートを伴う。<br> <br> GROUP BYカラムをインデックスに含めることで、インデックススキャンのみで完結する。<br> 例えば、CREATE INDEX idx_status ON orders(status) を作成し、SELECT status, COUNT(*) FROM orders GROUP BY status を実行する。<br> <br> 複合インデックスでのグループ化では、GROUP BYカラムがインデックスの左から連続している必要がある。<br> 例えば、INDEX(A, B, C) は GROUP BY A, B または GROUP BY A, B, C に使用可能である。<br> GROUP BY B, C または GROUP BY A, C には使用できない。<br> <br> DISTINCTは内部的にGROUP BYに変換される。<br> インデックスが使用できる場合は高速である。<br> 例えば、SELECT DISTINCT status FROM orders (status にインデックス) のように記述する。<br> <br> 一時テーブルサイズとして、<code>tmp_table_size</code> と <code>max_heap_table_size</code> を増加する。<br> デフォルトは16MB (tmp_table_size)、16MB (max_heap_table_size) である。<br> メモリ内一時テーブルがディスクに書き出される閾値であり、両方を同じ値に設定する必要がある。<br> 大規模なGROUP BYで効果的である。<br> <br><br> == InnoDB パフォーマンス == InnoDBは、MySQLのデフォルトストレージエンジンである。<br> <br> ==== バッファプール ==== バッファプールは、InnoDBの最も重要なメモリ領域である。<br> <br> <code>innodb_buffer_pool_size</code> のデフォルト値は128[MB]である。<br> 専用データベースサーバでは、物理メモリの70〜80[%]を割り当てることが推奨されており、例えば、16[GB] RAMサーバでは12[GB] (12884901888バイト) 程度を設定する。<br> <br> サーバの再起動なしで動的に変更可能であり、以下のように設定する。<br> <br> <syntaxhighlight lang="mysql"> SET GLOBAL innodb_buffer_pool_size = 12884901888; </syntaxhighlight> <br> 下表に、バッファプール関連パラメータを示す。<br> <br> <center> {| class="wikitable" |+ バッファプール関連パラメータ ! パラメータ !! デフォルト値 !! 説明 |- | innodb_buffer_pool_size || 128MB || バッファプールの合計サイズ |- | innodb_buffer_pool_instances || 8 (MySQL 8.0以降) || バッファプールのインスタンス数<br>複数インスタンスで並行性を向上<br>推奨: 最低1[GB]/インスタンス、つまり8[GB]バッファプールで8インスタンス |- | innodb_buffer_pool_chunk_size || 128MB (134217728バイト) || バッファプールの最小割り当て単位<br>動的リサイズ時にチャンク単位で追加 / 削除される。 |} </center> <br> 制約として、バッファプールサイズは <code>innodb_buffer_pool_chunk_size</code> × <code>innodb_buffer_pool_instances</code> の倍数である必要がある。<br> <br> 再起動後のウォームアップ時間を短縮するために、以下の設定を行う。<br> <br> <syntaxhighlight lang="ini"> innodb_buffer_pool_dump_at_shutdown = ON innodb_buffer_pool_load_at_startup = ON </syntaxhighlight> <br> この設定により、シャットダウン時にバッファプールの内容がダンプファイル <code>ib_buffer_pool</code> (データディレクトリ内) に保存され、起動時に自動復元される。<br> <br> 手動ダンプ・ロードは、以下のコマンドで実行する。<br> <br> <syntaxhighlight lang="mysql"> SET GLOBAL innodb_buffer_pool_dump_now = ON; SET GLOBAL innodb_buffer_pool_load_now = ON; </syntaxhighlight> <br> バッファプールの状態は、以下のコマンドで監視する。<br> <br> <syntaxhighlight lang="mysql"> SHOW ENGINE INNODB STATUS; </syntaxhighlight> <br> 出力結果のBuffer pool hit rate (キャッシュヒット率) を確認する。<br> 99[%]以上が理想的であり、90[%]未満の場合はバッファプールサイズの増加を検討する。<br> <br> ==== REDOログ ==== REDOログは、トランザクションの永続性を保証する。<br> <br> MySQL 8.0.30以降では、<code>innodb_redo_log_capacity</code> パラメータでREDOログ容量を設定する。<br> デフォルトは100[MB] (104857600バイト) であり、8[MB]〜128[GB]の範囲で設定可能である。<br> 動的変更に対応しており、以下のように設定する。<br> <br> <syntaxhighlight lang="mysql"> SET GLOBAL innodb_redo_log_capacity = 536870912; </syntaxhighlight> <br> MySQL 8.0.30以降では、<code>#innodb_redo</code> ディレクトリ内に32個のREDOログファイルが自動管理される。<br> 従来の <code>innodb_log_file_size</code> および <code>innodb_log_files_in_group</code> パラメータは廃止されている。<br> <br> MySQL 8.0.29以前では、以下のパラメータを使用する。<br> <br> <center> {| class="wikitable" |+ 旧REDOログパラメータ (MySQL 8.0.29以前) ! パラメータ !! デフォルト値 !! 説明 |- | innodb_log_file_size || 48MB || 各REDOログファイルのサイズ |- | innodb_log_files_in_group || 2 || REDOログファイルの数 |} </center> <br> 合計REDOログサイズは、<u>innodb_log_file_size ✕ innodb_log_files_in_group</u> で算出される。<br> <u>なお、これらのパラメータの変更にはサーバのシャットダウンが必要である。</u><br> <br> 下表に、推奨設定を示す。<br> <br> <center> {| class="wikitable" |+ REDOログ容量の推奨設定 ! 環境 !! 推奨容量 !! 説明 |- | OLTP環境 || 512[MB] 〜 1[GB] || 短時間の読み書きトランザクションが多数 |- | トランザクション量が多い環境 || 1[GB] 〜 4[GB] || バッチ処理やデータインポートが頻繁 |} </center> <br> REDOログ容量が小さすぎる場合、チェックポイントが頻繁に発生してディスクI/Oが増加し、パフォーマンスが低下する。<br> <br> 下表に、REDOログフラッシュの設定を示す。<br> <br> <center> {| class="wikitable" |+ innodb_flush_log_at_trx_commit ! 値 !! 説明 |- | 0 || 1秒ごとにディスクにフラッシュ<br>最速だが、クラッシュ時に最大1秒分のトランザクションを失う。 |- | 1 || 各トランザクションコミット時にディスクにフラッシュ (デフォルト)<br>最も安全である。 |- | 2 || 各トランザクションコミット時にOSバッファにフラッシュ、1秒ごとにディスクにフラッシュ |} </center> <br> ==== チェンジバッファ ==== チェンジバッファは、セカンダリインデックスへの変更をバッファリングする機能である。<br> <br> <code>innodb_change_buffering</code> のデフォルト値は <u>all</u> であり、<br> 設定可能な値は、<u>all</u>、<u>none</u>、<u>inserts</u>、<u>deletes</u>、<u>changes</u>、<u>purges</u> である。<br> <br> 下表に、主な設定値を示す。<br> <br> <center> {| class="wikitable" |+ チェンジバッファ設定一覧 ! 設定値 !! 説明 |- | all || INSERT、DELETE、UPDATEの全てをバッファリングする。 |- | inserts || INSERTのみバッファリングする。 |- | none || チェンジバッファを無効化する。 |} </center> <br> 動作原理として、セカンダリインデックスへの変更がバッファに記録され、ランダムディスクI/Oが削減される。<br> バッファに記録された変更は、後でバックグラウンドプロセスによりマージされる。<br> なお、<code>PRIMARY KEY</code> への変更は対象外であり、常に即座に更新される。<br> <br> セカンダリインデックスが多いテーブルや、INSERT / UPDATE / DELETE が頻繁なワークロードにおいて特に効果が大きい。<br> <br> <code>innodb_change_buffer_max_size</code> のデフォルト値は <u>25 (バッファプールの25[%])</u> であり、<u>0〜50</u> の範囲で設定する。<br> 値が大きいほど、より多くの変更をバッファリングできる。<br> <br> ==== ダブルライトバッファ ==== ダブルライトバッファは、部分的なページ書き込みが発生した際のクラッシュ復旧を保証する機能である。<br> <br> MySQL 8.0.30以降では、<code>innodb_doublewrite</code> パラメータで設定する。<br> デフォルトは <code>ON</code> であり、設定可能な値は以下の通りである。<br> <br> <center> {| class="wikitable" |+ ダブルライトバッファ設定一覧 ! 設定値 !! 説明 |- | ON || ダブルライトバッファを有効化する。(通常の動作) |- | OFF || ダブルライトバッファを無効化する。(パフォーマンス向上、安全性低下) |- | DETECT_ONLY || 破損検出のみを行い、復旧は行わない。 |- | DETECT_AND_RECOVER || 破損検出と復旧を行う。(<u>ON</u> と同等) |} </center> <br> 動作原理は以下の通りである。<br> <br> # まず、ページをダブルライトバッファに書き込む。(シーケンシャル書き込みのため高速) # 次に、実際のデータファイルに書き込む。(ランダム書き込み) # クラッシュにより部分的な書き込みが発生した場合、ダブルライトバッファから復旧する。 <br> パフォーマンスへの影響として、<u>ON</u> 時には書き込みが2回発生するため、10〜20[%]程度のオーバーヘッドがある。<br> ただし、SSDではオーバーヘッドは比較的小さい。<br> <br> <u>OFF</u> に設定する場合は、部分的な書き込み時にデータ破損のリスクがあることに注意する。<br> ファイルシステムがアトミック書き込みをサポートする場合 (例: Fusion-io NVMe) にのみ、無効化を検討する。<br> <br> ==== MVCC とトランザクション ==== Multi-Version Concurrency Control (MVCC) は、InnoDBの同時実行制御機構である。<br> <br> 下表に、分離レベルとパフォーマンスを示す。<br> <br> <center> {| class="wikitable" |+ トランザクション分離レベル ! 分離レベル !! 説明 |- | READ UNCOMMITTED || ダーティリード許容、最小オーバーヘッド<br>本番環境での使用は非推奨 |- | READ COMMITTED || 各ステートメント開始時にスナップショット取得<br>パフォーマンス良好、多くのOLTPアプリケーションに適合<br>バイナリログ形式はROWである必要がある。 |- | REPEATABLE READ || デフォルト<br>トランザクション開始時にスナップショット取得<br>長時間トランザクションでUNDOログが肥大化<br>ファントムリード防止 |- | SERIALIZABLE || 完全分離、全SELECTに共有ロック<br>ロック多発、パフォーマンス低い。<br>デッドロックが発生しやすい |} </center> <br> <u>トランザクションサイズの管理について、以下に示す事柄に注意する。</u><br> * <u>長時間トランザクションを避ける。</u> *: UNDOログが肥大化してバッファプールを圧迫し、他のトランザクションがブロックされる可能性がある。 *: 適切なサイズに分割することを推奨する。 *: 例: 100万行のUPDATEを1万行ずつに分割する。 <br> <code>autocommit</code> の設定について以下に示す。<br> <br> * <code>autocommit</code> のデフォルト値は <u>1 (ON)</u> であり、各ステートメントが自動的にコミットされる。 * 明示的なトランザクションを使用する場合は、<u>autocommit = 1</u> のままで <u>BEGIN</u> を使用する。 <br> ==== I/O設定 ==== 下表に、InnoDBのI/O動作を制御するパラメータを示す。<br> <br> <center> {| class="wikitable" |+ I/O容量パラメータ ! パラメータ !! デフォルト値 !! 説明 |- | innodb_io_capacity || 200 IOPS || バックグラウンドI/O操作の上限<br>HDD: 100-200<br>SSD: 1000-10000以上<br><br>ストレージのIOPS性能に合わせて設定 |- | innodb_io_capacity_max || 2000 IOPS (innodb_io_capacityの2倍) || バッファプールフラッシュが遅延した場合の上限<br>通常は、<u>innodb_io_capacity</u> の2倍を設定 |} </center> <br> 下表に、フラッシュメソッドを示す。<br> <br> <center> {| class="wikitable" |+ innodb_flush_method ! 値 !! 説明 |- | fsync || 標準のfsync()システムコール。デフォルト (Unix系) |- | O_DIRECT || ファイルシステムキャッシュをバイパス。Linux推奨。ダブルバッファリング (ファイルシステムキャッシュとバッファプール) を回避。メモリ使用量が削減される |- | O_DSYNC || データの同期書き込み |} </center> <br> Linux環境では、<u>O_DIRECT</u> が推奨される。<br> <br> * <code>innodb_read_ahead_threshold</code> *: デフォルト値は56 (ページ数) であり、0〜64の範囲で設定する。 *: エクステント (64ページ) 内でこの閾値を超える数のページが順次読み込まれた場合、残りのページが先読みされる。 *: シーケンシャルスキャンが多いワークロードで効果的である。 <br><br> == オプティマイザヒントとインデックスヒント == オプティマイザの実行計画を制御する手法である。<br> <br> ==== オプティマイザヒント (MySQL 8.0) ==== オプティマイザヒントは、クエリの実行計画に影響を与える。<br> <br> 構文を以下に示す。<br> <br> <syntaxhighlight lang="mysql"> SELECT /*+ HINT_NAME(parameters) */ ... </syntaxhighlight> <br> 下表に、主要なヒントを示す。<br> <br> <center> {| class="wikitable" |+ 主要なオプティマイザヒント ! ヒント !! 説明 |- | BKA / NO_BKA || Batched Key Access を有効化/無効化<br>例: <u>SELECT /*+ BKA(t1) */ * FROM t1 JOIN t2 ON t1.id = t2.t1_id</u> |- | INDEX_MERGE / NO_INDEX_MERGE || インデックスマージを有効化 / 無効化<br>例: <u>SELECT /*+ NO_INDEX_MERGE(t1) */ * FROM t1 WHERE a = 10 OR b = 20</u> |- | JOIN_FIXED_ORDER || FROM句の順序でJOINを実行<br>例: <u>SELECT /*+ JOIN_FIXED_ORDER() */ * FROM t1 JOIN t2 JOIN t3</u> |- | JOIN_ORDER || 指定した順序でJOINを実行<br>例: <u>SELECT /*+ JOIN_ORDER(t1, t3, t2) */ * FROM t1 JOIN t2 JOIN t3</u> |- | JOIN_PREFIX || 最初のテーブル順序を指定<br>例: <u>SELECT /*+ JOIN_PREFIX(t1, t2) */ * FROM t1 JOIN t2 JOIN t3</u> |- | JOIN_SUFFIX || 最後のテーブル順序を指定<br>例: <u>SELECT /*+ JOIN_SUFFIX(t2, t3) */ * FROM t1 JOIN t2 JOIN t3</u> |- | MAX_EXECUTION_TIME || クエリの最大実行時間 (ミリ秒単位) を制限<br>例: <u>SELECT /*+ MAX_EXECUTION_TIME(1000) */ * FROM large_table</u> |- | QB_NAME || クエリブロックに名前を付ける。<br>サブクエリに対してヒントを適用する時に使用する。<br>例: <u>SELECT /*+ QB_NAME(qb1) */ * FROM t1 WHERE id IN (SELECT /*+ QB_NAME(qb2) */ id FROM t2)</u> |- | SEMIJOIN / NO_SEMIJOIN || 半結合最適化を制御<br>例: <u>SELECT /*+ SEMIJOIN(@subq1 MATERIALIZATION) */ * FROM t1 WHERE id IN (SELECT /*+ QB_NAME(subq1) */ id FROM t2)</u> |} </center> <br> 下表に、インデックスヒント (オプティマイザヒント形式) を示す。<br> <br> <center> {| class="wikitable" |+ インデックスヒント (オプティマイザヒント形式) ! ヒント !! 説明 |- | INDEX || 指定したインデックスの使用を提案<br>例: <u>SELECT /*+ INDEX(t1 idx_name) */ * FROM t1 WHERE name = 'Smith'</u> |- | NO_INDEX || 指定したインデックスを無視<br>例: <u>SELECT /*+ NO_INDEX(t1 idx_name) */ * FROM t1 WHERE name = 'Smith'</u> |- | INDEX_MERGE || インデックスマージを有効化<br>例: <u>SELECT /*+ INDEX_MERGE(t1 idx1, idx2) */ * FROM t1 WHERE a = 10 OR b = 20</u> |} </center> <br> ==== インデックスヒント ==== テーブル名の後にインデックスヒントを記述する旧形式である。<br> <br> <code>USE INDEX</code> は、指定したインデックスの使用を提案する。<br> オプティマイザは他のインデックスも考慮する。<br> <br> <syntaxhighlight lang="mysql"> SELECT * FROM users USE INDEX (idx_name) WHERE name = 'Smith'; </syntaxhighlight> <br> <code>FORCE INDEX</code> は、指定したインデックスの使用を強制する。<br> テーブルスキャンよりインデックススキャンを優先する。<br> <br> <syntaxhighlight lang="mysql"> SELECT * FROM users FORCE INDEX (idx_name) WHERE name = 'Smith'; </syntaxhighlight> <br> <code>IGNORE INDEX</code> は、指定したインデックスを無視する。<br> 他のインデックスまたはテーブルスキャンが選択される。<br> <br> <syntaxhighlight lang="mysql"> SELECT * FROM users IGNORE INDEX (idx_name) WHERE name = 'Smith'; </syntaxhighlight> <br> <code>FOR</code> 句により、ヒントの適用範囲を指定する。<br> <br> * <code>FOR JOIN</code> は、JOIN処理に適用する。 * <code>FOR ORDER BY</code> は、<code>ORDER BY</code> 処理に適用する。 * <code>FOR GROUP BY</code> は、<code>GROUP BY</code> 処理に適用する。 <br> <syntaxhighlight lang="mysql"> SELECT * FROM users USE INDEX FOR ORDER BY (idx_order_date) ORDER BY order_date; </syntaxhighlight> <br> インデックスヒントは将来のバージョンで非推奨になる可能性がある。<br> オプティマイザヒントへの移行が推奨される。<br> <br> データ分布が変化するとヒントが不適切になるため、定期的な見直しが必要である。<br> <br> ==== SET_VARヒント ==== クエリ実行中にセッション変数を一時的に変更する。<br> <br> これは、長時間実行クエリのタイムアウト設定に使用する。<br> <br> 構文を以下に示す。<br> <br> <syntaxhighlight lang="mysql"> SELECT /*+ SET_VAR(var_name = value) */ ... </syntaxhighlight> <br> * 使用例 *: <syntaxhighlight lang="mysql"> SELECT /*+ SET_VAR(sort_buffer_size = 16M) */ * FROM large_table ORDER BY created_at; </syntaxhighlight> *: <br> * 大規模なソートで効果的である。 *: <syntaxhighlight lang="mysql"> SELECT /*+ SET_VAR(tmp_table_size = 64M) */ status, COUNT(*) FROM orders GROUP BY status; </syntaxhighlight> *: <br> * 大規模なグループ化で効果的である。 *: <syntaxhighlight lang="mysql"> SELECT /*+ SET_VAR(max_execution_time = 5000) */ * FROM slow_table; </syntaxhighlight> <br> セッション全体の設定を変更せずに済むため、他のクエリに影響しない。<br> アプリケーションコードを変更せずにチューニング可能である。(SET SESSION を発行する必要がない)<br> <br><br> == ヒストグラム統計 (MySQL 8.0) == ヒストグラムは、オプティマイザがより正確な実行計画を選択するための統計情報である。<br> <br> ==== ヒストグラムの概要 ==== ヒストグラムは、カラムの値の分布を記録する。<br> <br> 従来の統計情報は、カーディナリティ (異なる値の数) のみを記録する。<br> 値の分布は考慮されない。<br> 例えば、1,000万行のうち、statusカラムは 'active' が999万行、'inactive' が1万行である場合、従来の統計ではカーディナリティ2としか認識されない。<br> <br> ヒストグラム統計は、値の分布を詳細に記録する。<br> WHERE status = 'inactive' が1万行のみを返すことをオプティマイザが認識する。<br> より正確な実行計画が選択される。<br> <br> ヒストグラムの種類を下表に示す。<br> <br> <center> {| class="wikitable" |+ ヒストグラムの種類 ! 種類 !! 説明 !! 適用対象 |- | singleton || 各値の出現頻度を個別に記録 || 異なる値が少ないカラム。例: ステータスカラム (5種類の値) |- | equi-height || 値をバケットに分割、各バケットの行数を均等化 || 異なる値が多いカラム。例: タイムスタンプカラム (数百万種類の値) |} </center> <br> ==== ヒストグラムの作成と管理 ==== ヒストグラムは手動で作成する必要がある。<br> <br> ヒストグラムの作成例を以下に示す。<br> <br> <syntaxhighlight lang="mysql"> ANALYZE TABLE <テーブル名> UPDATE HISTOGRAM ON column_name WITH 256 BUCKETS; </syntaxhighlight> <br> * 複数カラムに対して作成する例 *: バケット数のデフォルトは <u>100</u> であり、範囲は <u>1 〜 1024</u> である。 *: バケット数が多いほど詳細な分布を記録する。 *: 256バケットが一般的な推奨値である。<br> *: <syntaxhighlight lang="mysql"> ANALYZE TABLE orders UPDATE HISTOGRAM ON status, order_date WITH 256 BUCKETS; </syntaxhighlight> *: <br> * ヒストグラムの削除例 *: <syntaxhighlight lang="mysql"> ANALYZE TABLE <テーブル名> DROP HISTOGRAM ON column_name; </syntaxhighlight> *: <br> * ヒストグラムの確認例 *: <syntaxhighlight lang="mysql"> SELECT * FROM information_schema.COLUMN_STATISTICS WHERE SCHEMA_NAME = 'database_name' AND TABLE_NAME = '<テーブル名>'; </syntaxhighlight> *: <br> * ヒストグラムの内容確認例 *: <syntaxhighlight lang="mysql"> SELECT SCHEMA_NAME, TABLE_NAME, COLUMN_NAME, JSON_PRETTY(HISTOGRAM) FROM information_schema.COLUMN_STATISTICS WHERE TABLE_NAME = 'orders' AND COLUMN_NAME = 'status'; </syntaxhighlight> <br> ヒストグラムは自動更新されない。(MySQL 8.0)<br> <br> データ分布が変化した後は手動で再作成が必要である。<br> <br> <code>ANALYZE TABLE ... UPDATE HISTOGRAM</code> を定期的に実行する。<br> <br> メモリへの影響は最小限である。<br> ヒストグラム情報はコンパクトであり、オンラインでの作成が可能である。<br> <br> ==== 効果的な使用場面 ==== ヒストグラムは、特定の状況で効果を発揮する。<br> <br> 値の分布が偏ったカラムとして、ステータスカラムがある。<br> 例えば、'active' が999万行、'inactive' が1万行である場合、WHERE status = 'inactive' がインデックススキャンを選択する。<br> <br> 日付カラムでは、過去1年のデータが大半、それ以前がわずかである場合、WHERE created_at < '2020-01-01' が適切な実行計画を選択する。<br> <br> カテゴリカラムでは、'category_A' が90[%]、他が各2[%]である場合、WHERE category = 'category_B' が小規模スキャンを選択する。<br> <br> 下表に、インデックスとの使い分けを示す。<br> <br> <center> {| class="wikitable" |+ インデックスとヒストグラムの使い分け ! 手法 !! 適している場合 |- | インデックス || WHERE句で頻繁に検索されるカラム<br>JOIN / ORDER BY / GROUP BYで使用されるカラム |- | ヒストグラム || インデックス作成のコストが高いカラム (TEXT, JSON等)<br>複数の等値検索パターンがあるカラム、メンテナンスオーバーヘッドを避けたいカラム |} </center> <br> <u>JOINの最適化において、ヒストグラムが無い場合、オプティマイザが不適切な駆動表を選択する可能性がある。</u><br> <u>ヒストグラムがある場合、行数推定が正確になり適切な駆動表が選択される。</u><br> <br><br> == Performance Schema と sysスキーマ == ==== Performance Schema の基本 ==== Performance Schemaは、MySQLの内部動作を監視するための機能である。<br> <br> Performance Schemaは、MySQL 8.0でデフォルトで有効化されている。<br> <br> <code>performance_schema</code> パラメータのデフォルト値はON (MySQL 8.0) である。<br> 起動時のみ変更可能であり、動的変更は不可である。<br> 設定ファイルの <code>[mysqld]</code> セクションに <code>performance_schema = ON</code> を記述する。<br> <br> 下表に、主要なテーブルを示す。<br> <br> <center> {| class="wikitable" |+ Performance Schema主要テーブル ! テーブル !! 説明 |- | events_statements_summary_by_digest || クエリの実行統計 (正規化されたクエリごと)<br>実行回数、合計実行時間、平均実行時間、最大 / 最小実行時間、行数の統計 |- | events_waits_summary_by_instance || 待機イベントの統計<br>ファイルI/O、テーブルロック、ミューテックス等 |- | table_io_waits_summary_by_table || テーブルごとのI/O統計<br>読み込み / 書き込み回数、待機時間 |- | file_summary_by_instance || ファイルごとのI/O統計<br>REDOログ、テーブルファイル、一時ファイル等 |} </center> <br> データのリセットは、以下に示すコマンドで実行する。<br> 定期的にリセットすることで、直近の統計情報のみを取得可能である。<br> <br> <syntaxhighlight lang="mysql"> TRUNCATE TABLE performance_schema.events_statements_summary_by_digest; </syntaxhighlight> <br> ==== sysスキーマの活用 ==== sysスキーマは、Performance Schemaのデータを読みやすい形式で提供する。<br> <br> 下表に、主要なビューを示す。<br> <br> <center> {| class="wikitable" |+ sys スキーマ主要ビュー ! ビュー !! 説明 |- | schema_unused_indexes || 未使用インデックスを検出。Performance Schema有効化後に収集されたデータに基づく |- | schema_tables_with_full_table_scans || フルテーブルスキャンが発生したテーブル。インデックスの追加を検討 |- | statements_with_full_table_scans || フルテーブルスキャンを実行したSQL文。クエリとインデックスの最適化が必要 |- | statements_with_runtimes_in_95th_percentile || 最も遅いクエリ (95パーセンタイル)。優先的に最適化すべきクエリ |- | statements_with_sorting || ソートを実行したクエリ。filesortが発生しているクエリを特定 |- | statements_with_temp_tables || 一時テーブルを使用したクエリ。メモリ/ディスク一時テーブルの使用状況 |- | host_summary / user_summary || ホスト/ユーザーごとの統計情報。接続数、実行時間、I/O量など |} </center> <br> ==== 代表的な監視クエリ ==== 実際の監視で使用するクエリ例を以下に示す。<br> <br> * 最も遅いクエリを取得する。 *: <syntaxhighlight lang="mysql"> SELECT DIGEST_TEXT AS query, COUNT_STAR AS exec_count, AVG_TIMER_WAIT / 1000000000000 AS avg_sec, SUM_TIMER_WAIT / 1000000000000 AS total_sec FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 10; </syntaxhighlight> *: <br> * フルテーブルスキャンが多いクエリを取得する。 *: <syntaxhighlight lang="mysql"> SELECT DIGEST_TEXT AS query, COUNT_STAR AS exec_count, SUM_NO_INDEX_USED AS no_index_count, SUM_NO_GOOD_INDEX_USED AS no_good_index_count FROM performance_schema.events_statements_summary_by_digest WHERE SUM_NO_INDEX_USED > 0 OR SUM_NO_GOOD_INDEX_USED > 0 ORDER BY SUM_NO_INDEX_USED + SUM_NO_GOOD_INDEX_USED DESC LIMIT 10; </syntaxhighlight> *: <br> * テーブルごとのI/O統計を取得する。 *: <syntaxhighlight lang="mysql"> SELECT OBJECT_SCHEMA AS db_name, OBJECT_NAME AS <テーブル名>, COUNT_READ, COUNT_WRITE, SUM_TIMER_READ / 1000000000000 AS read_sec, SUM_TIMER_WRITE / 1000000000000 AS write_sec FROM performance_schema.table_io_waits_summary_by_table WHERE OBJECT_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys') ORDER BY SUM_TIMER_READ + SUM_TIMER_WRITE DESC LIMIT 10; </syntaxhighlight> *: <br> * 未使用インデックスを取得する。 *: <syntaxhighlight lang="mysql"> SELECT object_schema AS db_name, object_name AS <テーブル名>, <インデックス名> FROM sys.schema_unused_indexes WHERE object_schema NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys'); </syntaxhighlight> *: <br> * 一時テーブルを使用したクエリを取得する。 *: <syntaxhighlight lang="mysql"> SELECT DIGEST_TEXT AS query, COUNT_STAR AS exec_count, SUM_CREATED_TMP_TABLES AS tmp_tables, SUM_CREATED_TMP_DISK_TABLES AS tmp_disk_tables FROM performance_schema.events_statements_summary_by_digest WHERE SUM_CREATED_TMP_TABLES > 0 ORDER BY SUM_CREATED_TMP_DISK_TABLES DESC LIMIT 10; </syntaxhighlight> <br> これらのクエリを定期的に実行し、パフォーマンス問題を早期に発見する。<br> <br><br> == クエリキャッシュの廃止 (MySQL 8.0) == <u>クエリキャッシュは、MySQL 8.0で完全に削除されている。</u><br> <br> ==== 廃止の経緯 ==== クエリキャッシュは、マルチコア環境でのスケーラビリティ問題により廃止された。<br> <br> MySQL 5.6では、クエリキャッシュがデフォルトで無効化された (<code>query_cache_type</code> = 0)。<br> パフォーマンス問題が認識された。<br> <br> MySQL 8.0では、クエリキャッシュが完全に削除された。<br> 関連するパラメータとヒントが廃止された。<br> <br> 廃止の理由として、グローバルロックによるスケーラビリティ問題がある。<br> クエリキャッシュへのアクセスはシングルミューテックスで保護される。<br> マルチコアCPUで並行性が低下し、同時実行数が増えるほどパフォーマンスが低下する。<br> <br> テーブル更新時のキャッシュ無効化も問題である。<br> テーブルに対するINSERT/UPDATE/DELETEで関連する全キャッシュが無効化される。<br> 更新が頻繁なテーブルではキャッシュヒット率が極めて低い。<br> OLTPワークロードでは効果が薄い。<br> <br> キャッシュメンテナンスのオーバーヘッドも大きい。<br> キャッシュの登録、検索、無効化がボトルネックとなる。<br> キャッシュを無効にした方が高速な場合が多い。<br> <br> 下表に、廃止されたパラメータおよびヒントを示す。<br> <br> <center> {| class="wikitable" |+ MySQL廃止されたパラメータとヒント ! タイプ !! 名称 |- | パラメータ || query_cache_type |- | パラメータ || query_cache_size |- | パラメータ || query_cache_limit |- | パラメータ || query_cache_min_res_unit |- | パラメータ || query_cache_wlock_invalidate |- | ヒント || SQL_CACHE |- | ヒント || SQL_NO_CACHE |} </center> <br> ==== 代替手段 ==== クエリキャッシュに代わる以下の手法が推奨される。<br> <br> ===== ProxySQL ===== ProxySQLはMySQL前段に配置されるSQL中間層(プロキシ)として動作し、クエリベースのキャッシング機能を提供する。<br> ルールベースのキャッシュポリシー設定が可能であり、併せてコネクションプーリング機能も実装している。 <br> メリットとして、以下が挙げられる。 * MySQL本体のパフォーマンスに影響しない。 * キャッシュヒット率とキャッシュ統計を監視可能である。 * 更新頻度に応じたキャッシュTTL設定が柔軟に行える。 <br> ===== Redis / Memcached ===== Redis / Memcachedはアプリケーション層でのキャッシング機構であり、クエリ結果をシリアライズして保存する。<br> アプリケーションコードでキャッシュロジックを実装し、TTL(Time To Live)を柔軟に設定可能である。<br> <br> メリットとして、以下が挙げられる。 * MySQLの負荷を根本的に削減できる。 * キャッシュの粒度を細かく制御可能である。 * 複数のMySQLサーバで共有可能である。 <br> ===== ReadySet ===== ReadySetはMySQL前段に配置されるSQLキャッシング専用エンジンである。<br> MySQLプロトコル互換性を備え、テーブル変更検知とキャッシュの自動無効化を行う。<br> アプリケーション変更不要の透過的なキャッシングが実現される。<br> <br> メリットとして、以下が挙げられる。 * アプリケーションコードの変更が不要である。 * テーブル更新時に影響を受けるキャッシュのみ無効化される。 * スケーラビリティが高い。 <br> ===== インデックスとクエリの最適化 ===== クエリキャッシュは問題の対症療法であり、インデックスとクエリの最適化が本質的な解決策である。<br> 適切なインデックス設計により、キャッシュに依存しない性能を実現できる。<br> <br> 根本的なパフォーマンス改善には、キャッシュ戦略と並行してクエリ最適化を推進することが重要である。<br> <br><br> == 参考リンク == * [https://dev.mysql.com/doc/refman/8.0/en/optimization.html MySQL 8.0 公式ドキュメント - Optimization] * [https://dev.mysql.com/doc/refman/8.0/en/innodb-storage-engine.html MySQL 8.0 公式ドキュメント - InnoDB Storage Engine] * [https://dev.mysql.com/doc/refman/8.0/en/performance-schema.html MySQL 8.0 公式ドキュメント - Performance Schema] * [https://dev.mysql.com/doc/refman/8.0/en/sys-schema.html MySQL 8.0 公式ドキュメント - sys Schema] * [https://dev.mysql.com/doc/refman/8.0/en/optimizer-hints.html MySQL 8.0 公式ドキュメント - Optimizer Hints] <br><br> {{#seo: |title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki |keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,MySQL,Database,Performance,Tuning,InnoDB,Index,Query,Optimization,EXPLAIN,Buffer Pool,Performance Schema,Histogram,Optimizer Hint,データベース,パフォーマンス,チューニング,インデックス,クエリ最適化 |description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux |image=/resources/assets/MochiuLogo_Single_Blue.png }} __FORCETOC__ [[カテゴリ:MySQL]]
MySQL - パフォーマンスチューニング
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse