インデックス設計
インデックス設計
Section titled “インデックス設計”データベースのインデックス設計について説明します。
なぜインデックスが必要なのか
Section titled “なぜインデックスが必要なのか”インデックスなしの検索
Section titled “インデックスなしの検索”問題:
-- インデックスなしのテーブルSELECT * FROM users WHERE email = 'john@example.com';-- 問題点:// - フルテーブルスキャンが発生// - 大量データの場合、非常に遅い// - O(n)の時間計算量インデックスありの検索:
-- インデックスありのテーブルCREATE INDEX idx_email ON users(email);
SELECT * FROM users WHERE email = 'john@example.com';-- メリット:// - インデックスを使用した高速検索// - O(log n)の時間計算量// - 大量データでも高速メリット:
- 検索速度: 検索が高速化
- ソート速度: ソートが高速化
- JOIN速度: JOINが高速化
インデックスの種類
Section titled “インデックスの種類”1. プライマリキーインデックス
Section titled “1. プライマリキーインデックス”自動的に作成される:
CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, -- プライマリキーインデックスが自動作成 name VARCHAR(255));2. ユニークインデックス
Section titled “2. ユニークインデックス”重複を防ぐ:
CREATE TABLE users ( id INT PRIMARY KEY, email VARCHAR(255) UNIQUE -- ユニークインデックスが自動作成);
-- または明示的にCREATE UNIQUE INDEX idx_email ON users(email);3. 通常のインデックス
Section titled “3. 通常のインデックス”検索を高速化:
CREATE INDEX idx_name ON users(name);4. 複合インデックス
Section titled “4. 複合インデックス”複数のカラムにインデックス:
CREATE INDEX idx_name_email ON users(name, email);複合インデックスの順序:
-- 良い例: よく検索されるカラムを先にCREATE INDEX idx_status_created ON orders(status, created_at);
-- 悪い例: 順序が不適切CREATE INDEX idx_created_status ON orders(created_at, status);インデックスの設計原則
Section titled “インデックスの設計原則”1. 検索頻度の高いカラム
Section titled “1. 検索頻度の高いカラム”WHERE句でよく使われるカラム:
-- よく検索されるカラムSELECT * FROM users WHERE email = 'john@example.com';CREATE INDEX idx_email ON users(email);2. JOINで使われるカラム
Section titled “2. JOINで使われるカラム”外部キーにインデックス:
-- JOINで使われるカラムSELECT * FROM orders oJOIN users u ON o.user_id = u.id;CREATE INDEX idx_user_id ON orders(user_id);3. ORDER BYで使われるカラム
Section titled “3. ORDER BYで使われるカラム”ソートで使われるカラム:
-- ソートで使われるカラムSELECT * FROM users ORDER BY created_at DESC;CREATE INDEX idx_created_at ON users(created_at);4. カバリングインデックス
Section titled “4. カバリングインデックス”クエリに必要なすべてのカラムを含む:
-- カバリングインデックスCREATE INDEX idx_user_covering ON users(id, name, email);
-- このクエリはインデックスのみで実行可能SELECT id, name, email FROM users WHERE id = 1;インデックスのデメリット
Section titled “インデックスのデメリット”1. ストレージ使用量
Section titled “1. ストレージ使用量”インデックスは追加のストレージを使用します。
2. 更新コスト
Section titled “2. 更新コスト”データの挿入、更新、削除時にインデックスも更新されます。
例:
-- インデックスが多いと更新が遅くなるINSERT INTO users (name, email) VALUES ('John', 'john@example.com');-- すべてのインデックスが更新される実践例: インデックス設計
Section titled “実践例: インデックス設計”-- ユーザーテーブルCREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, status VARCHAR(20) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_status (status), INDEX idx_created_at (created_at), INDEX idx_status_created (status, created_at) -- 複合インデックス);
-- 注文テーブルCREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, status VARCHAR(20) NOT NULL, total DECIMAL(10,2) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id), INDEX idx_user_id (user_id), INDEX idx_status (status), INDEX idx_created_at (created_at), INDEX idx_user_status (user_id, status) -- 複合インデックス);インデックスの確認
Section titled “インデックスの確認”1. インデックスの一覧
Section titled “1. インデックスの一覧”SHOW INDEXES FROM users;2. クエリの実行計画
Section titled “2. クエリの実行計画”EXPLAIN SELECT * FROM users WHERE email = 'john@example.com';実行計画の確認ポイント:
- type:
refやrangeが理想的(ALLは避ける) - key: 使用されているインデックス
- rows: スキャンされる行数
インデックス設計のポイント:
- 検索頻度: よく検索されるカラムにインデックス
- JOIN: 外部キーにインデックス
- ソート: ORDER BYで使われるカラムにインデックス
- 複合インデックス: よく一緒に検索されるカラムに複合インデックス
- デメリット: ストレージ使用量と更新コストを考慮
適切なインデックス設計により、クエリのパフォーマンスを大幅に向上できます。
4. インデックス戦略
Section titled “4. インデックス戦略”インデックス戦略の重要性
Section titled “インデックス戦略の重要性”インデックス戦略は、パフォーマンスとコストのバランスを取るための重要な設計要素です。
戦略1: カーディナリティに基づく戦略
Section titled “戦略1: カーディナリティに基づく戦略”カーディナリティとは
Section titled “カーディナリティとは”カーディナリティは、カラム内の一意な値の数です。
カーディナリティの種類:
- 高カーディナリティ: 一意な値が多い(例: email、user_id)
- 中カーディナリティ: 一意な値が中程度(例: status、category_id)
- 低カーディナリティ: 一意な値が少ない(例: gender、is_active)
実装例:
-- 高カーディナリティ: インデックスが効果的CREATE INDEX idx_email ON users(email); -- 各ユーザーが異なるemail
-- 中カーディナリティ: 複合インデックスが効果的CREATE INDEX idx_status_created ON orders(status, created_at);-- status: 'pending', 'processing', 'completed', 'cancelled' (4種類)-- created_at: 日時(高カーディナリティ)
-- 低カーディナリティ: 単独インデックスは効果が薄い-- CREATE INDEX idx_gender ON users(gender); -- 'male', 'female' (2種類のみ)-- 複合インデックスで使用するCREATE INDEX idx_gender_created ON users(gender, created_at);戦略2: 複合インデックスの順序戦略
Section titled “戦略2: 複合インデックスの順序戦略”複合インデックスの最適な順序
Section titled “複合インデックスの最適な順序”複合インデックスの順序は、クエリのパフォーマンスに大きく影響します。
原則:
- 等価条件(=)のカラムを先に
- 範囲条件(<, >, BETWEEN)のカラムを後に
- カーディナリティの高いカラムを先に
実装例:
-- 良い例: 等価条件を先に、範囲条件を後にCREATE INDEX idx_user_status_created ON orders(user_id, status, created_at);
-- このクエリは効率的SELECT * FROM ordersWHERE user_id = 123 AND status = 'completed' AND created_at BETWEEN '2024-01-01' AND '2024-12-31';
-- 悪い例: 順序が不適切CREATE INDEX idx_created_status_user ON orders(created_at, status, user_id);
-- このクエリは非効率(インデックスが部分的にしか使えない)SELECT * FROM ordersWHERE user_id = 123 AND status = 'completed' AND created_at BETWEEN '2024-01-01' AND '2024-12-31';戦略3: 部分インデックス(Partial Index)
Section titled “戦略3: 部分インデックス(Partial Index)”部分インデックスとは
Section titled “部分インデックスとは”条件を満たす行のみにインデックスを作成する方法です。
実装例:
-- PostgreSQL: アクティブなユーザーのみにインデックスCREATE INDEX idx_active_users_email ON users(email)WHERE status = 'active';
-- このクエリは高速SELECT * FROM users WHERE email = 'john@example.com' AND status = 'active';
-- MySQL: 関数インデックス(MySQL 8.0以降)CREATE INDEX idx_active_users_email ON users((CASE WHEN status = 'active' THEN email END));
-- メリット:-- 1. インデックスサイズが小さい-- 2. 更新コストが低い-- 3. クエリが高速戦略4: カバリングインデックス(Covering Index)
Section titled “戦略4: カバリングインデックス(Covering Index)”カバリングインデックスとは
Section titled “カバリングインデックスとは”クエリに必要なすべてのカラムを含むインデックスです。
実装例:
-- カバリングインデックスCREATE INDEX idx_user_covering ON users(id, name, email, status);
-- このクエリはインデックスのみで実行可能(テーブルアクセス不要)SELECT id, name, email, status FROM users WHERE id = 123;
-- メリット:-- 1. テーブルアクセスが不要-- 2. クエリが非常に高速-- 3. I/Oが削減される
-- デメリット:-- 1. インデックスサイズが大きくなる-- 2. 更新コストが高くなる戦略5: インデックスの監視とメンテナンス
Section titled “戦略5: インデックスの監視とメンテナンス”インデックスの使用状況の確認
Section titled “インデックスの使用状況の確認”実装例:
-- MySQL: インデックスの使用状況を確認SELECT TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX, COLUMN_NAME, CARDINALITYFROM INFORMATION_SCHEMA.STATISTICSWHERE TABLE_SCHEMA = 'my_database' AND TABLE_NAME = 'users'ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX;
-- PostgreSQL: インデックスの使用状況を確認SELECT schemaname, tablename, indexname, idx_scan as index_scans, idx_tup_read as tuples_read, idx_tup_fetch as tuples_fetchedFROM pg_stat_user_indexesWHERE schemaname = 'public'ORDER BY idx_scan DESC;
-- 使用されていないインデックスの特定SELECT schemaname, tablename, indexname, idx_scanFROM pg_stat_user_indexesWHERE idx_scan = 0 AND schemaname = 'public';インデックスの再構築
Section titled “インデックスの再構築”実装例:
-- MySQL: インデックスの再構築ALTER TABLE users DROP INDEX idx_email;ALTER TABLE users ADD INDEX idx_email (email);
-- またはANALYZE TABLE users;
-- PostgreSQL: インデックスの再構築REINDEX INDEX idx_email;REINDEX TABLE users;
-- またはVACUUM ANALYZE users;5. その他の重要なデータベース設計知識
Section titled “5. その他の重要なデータベース設計知識”知識1: パーティショニング
Section titled “知識1: パーティショニング”パーティショニングとは
Section titled “パーティショニングとは”大きなテーブルを小さなパーティションに分割する方法です。
パーティショニングの種類:
- 範囲パーティショニング: 日付や数値の範囲で分割
- リストパーティショニング: 特定の値のリストで分割
- ハッシュパーティショニング: ハッシュ値で分割
実装例:
-- MySQL: 範囲パーティショニング(日付で分割)CREATE TABLE orders ( id INT PRIMARY KEY, user_id INT NOT NULL, total DECIMAL(10,2) NOT NULL, created_at DATE NOT NULL) PARTITION BY RANGE (YEAR(created_at)) ( PARTITION p2022 VALUES LESS THAN (2023), PARTITION p2023 VALUES LESS THAN (2024), PARTITION p2024 VALUES LESS THAN (2025), PARTITION p_future VALUES LESS THAN MAXVALUE);
-- メリット:-- 1. クエリが高速(必要なパーティションのみスキャン)-- 2. メンテナンスが容易(古いパーティションを削除)-- 3. バックアップが容易
-- PostgreSQL: 範囲パーティショニングCREATE TABLE orders ( id INT PRIMARY KEY, user_id INT NOT NULL, total DECIMAL(10,2) NOT NULL, created_at DATE NOT NULL) PARTITION BY RANGE (created_at);
CREATE TABLE orders_2022 PARTITION OF orders FOR VALUES FROM ('2022-01-01') TO ('2023-01-01');CREATE TABLE orders_2023 PARTITION OF orders FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');CREATE TABLE orders_2024 PARTITION OF orders FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');知識2: ビュー(View)
Section titled “知識2: ビュー(View)”仮想的なテーブルで、クエリの結果を保存します。
実装例:
-- ビューの作成CREATE VIEW active_users ASSELECT id, name, email, created_atFROM usersWHERE status = 'active' AND deleted_at IS NULL;
-- ビューの使用SELECT * FROM active_users WHERE email = 'john@example.com';
-- メリット:-- 1. 複雑なクエリを簡素化-- 2. セキュリティ(特定のカラムのみ公開)-- 3. 一貫性(共通のロジックを共有)
-- マテリアライズドビュー(PostgreSQL)CREATE MATERIALIZED VIEW user_statistics ASSELECT DATE(created_at) as date, COUNT(*) as user_count, COUNT(CASE WHEN status = 'active' THEN 1 END) as active_countFROM usersGROUP BY DATE(created_at);
-- マテリアライズドビューの更新REFRESH MATERIALIZED VIEW user_statistics;知識3: ストアドプロシージャとトリガー
Section titled “知識3: ストアドプロシージャとトリガー”ストアドプロシージャとは
Section titled “ストアドプロシージャとは”データベース内に保存されたSQLコードです。
実装例:
-- MySQL: ストアドプロシージャDELIMITER //CREATE PROCEDURE GetUserOrders(IN user_id INT)BEGIN SELECT o.*, u.name, u.email FROM orders o JOIN users u ON o.user_id = u.id WHERE o.user_id = user_id ORDER BY o.created_at DESC;END //DELIMITER ;
-- ストアドプロシージャの呼び出しCALL GetUserOrders(123);
-- PostgreSQL: ストアドプロシージャCREATE OR REPLACE FUNCTION get_user_orders(p_user_id INT)RETURNS TABLE ( order_id INT, total DECIMAL, created_at TIMESTAMP, user_name VARCHAR, user_email VARCHAR) AS $$BEGIN RETURN QUERY SELECT o.id, o.total, o.created_at, u.name, u.email FROM orders o JOIN users u ON o.user_id = u.id WHERE o.user_id = p_user_id ORDER BY o.created_at DESC;END;$$ LANGUAGE plpgsql;
-- ストアドプロシージャの呼び出しSELECT * FROM get_user_orders(123);トリガーとは
Section titled “トリガーとは”特定のイベントが発生した際に自動的に実行されるコードです。
実装例:
-- MySQL: トリガー(更新日時の自動更新)DELIMITER //CREATE TRIGGER update_users_updated_atBEFORE UPDATE ON usersFOR EACH ROWBEGIN SET NEW.updated_at = CURRENT_TIMESTAMP;END //DELIMITER ;
-- PostgreSQL: トリガーCREATE OR REPLACE FUNCTION update_updated_at_column()RETURNS TRIGGER AS $$BEGIN NEW.updated_at = CURRENT_TIMESTAMP; RETURN NEW;END;$$ LANGUAGE plpgsql;
CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();知識4: データ型の選択
Section titled “知識4: データ型の選択”適切なデータ型の選択
Section titled “適切なデータ型の選択”データ型の選択は、パフォーマンスとストレージ使用量に影響します。
実装例:
-- 良い例: 適切なデータ型CREATE TABLE users ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, -- INTで十分 email VARCHAR(255) NOT NULL, -- 適切な長さ status ENUM('active', 'inactive', 'suspended') NOT NULL, -- ENUMで制約 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- TIMESTAMPで十分 balance DECIMAL(10,2) NOT NULL DEFAULT 0.00 -- 金額はDECIMAL);
-- 悪い例: 不適切なデータ型CREATE TABLE users ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, -- INTで十分なのにBIGINT email TEXT NOT NULL, -- VARCHARで十分なのにTEXT status VARCHAR(50) NOT NULL, -- ENUMで制約すべき created_at DATETIME DEFAULT CURRENT_TIMESTAMP, -- TIMESTAMPで十分 balance FLOAT NOT NULL DEFAULT 0.00 -- 金額はDECIMAL(精度の問題));
-- データ型の選択基準:-- 1. 必要な範囲をカバーする最小の型-- 2. 精度が必要な場合はDECIMAL-- 3. 固定長の場合はCHAR、可変長の場合はVARCHAR-- 4. 日時はTIMESTAMP(タイムゾーン対応)またはDATETIME知識5: 外部キー制約と参照整合性
Section titled “知識5: 外部キー制約と参照整合性”外部キー制約とは
Section titled “外部キー制約とは”テーブル間の参照整合性を保証する制約です。
実装例:
-- 外部キー制約の設定CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, total DECIMAL(10,2) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT -- ユーザー削除時は注文を削除しない ON UPDATE CASCADE -- ユーザーID更新時は注文も更新);
-- 外部キー制約のオプション:-- ON DELETE RESTRICT: 削除を禁止(デフォルト)-- ON DELETE CASCADE: 親レコード削除時に子レコードも削除-- ON DELETE SET NULL: 親レコード削除時に子レコードの外部キーをNULLに-- ON DELETE NO ACTION: 何もしない(RESTRICTと類似)
-- ON UPDATE CASCADE: 親レコード更新時に子レコードも更新-- ON UPDATE RESTRICT: 更新を禁止-- ON UPDATE SET NULL: 親レコード更新時に子レコードの外部キーをNULLに知識6: トランザクション分離レベル
Section titled “知識6: トランザクション分離レベル”トランザクション分離レベルとは
Section titled “トランザクション分離レベルとは”トランザクション間のデータの見え方を制御する設定です。
分離レベルの種類:
- READ UNCOMMITTED: コミット前のデータも読める(ダーティリード)
- READ COMMITTED: コミット済みのデータのみ読める(デフォルト)
- REPEATABLE READ: 同じトランザクション内で同じデータを読める
- SERIALIZABLE: 完全に分離(最も厳格)
実装例:
-- トランザクション分離レベルの設定SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;SELECT * FROM users WHERE id = 123;-- 他のトランザクションの変更がコミットされると見えるCOMMIT;
-- REPEATABLE READ(MySQLのデフォルト)SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;SELECT * FROM users WHERE id = 123;-- 同じトランザクション内では同じデータが見えるCOMMIT;
-- SERIALIZABLE(最も厳格)SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;SELECT * FROM users WHERE id = 123;-- 他のトランザクションは待機COMMIT;知識7: クエリパフォーマンスの分析
Section titled “知識7: クエリパフォーマンスの分析”EXPLAINの活用
Section titled “EXPLAINの活用”実装例:
-- MySQL: EXPLAINEXPLAIN SELECT * FROM usersWHERE email = 'john@example.com' AND status = 'active';
-- 重要な項目:-- type: ALL(全件スキャン)は避ける、ref/rangeが理想的-- key: 使用されているインデックス-- rows: スキャンされる行数(少ないほど良い)-- Extra: Using index(カバリングインデックス)が理想的
-- PostgreSQL: EXPLAIN ANALYZEEXPLAIN ANALYZE SELECT * FROM usersWHERE email = 'john@example.com' AND status = 'active';
-- 実際の実行時間も表示される知識8: データベース接続プール
Section titled “知識8: データベース接続プール”接続プールとは
Section titled “接続プールとは”データベース接続を再利用する仕組みです。
実装例:
# Python (SQLAlchemy): 接続プールの設定from sqlalchemy import create_engine
engine = create_engine( 'postgresql://user:password@localhost/dbname', pool_size=10, # 接続プールのサイズ max_overflow=20, # 追加の接続数 pool_pre_ping=True, # 接続の有効性を確認 pool_recycle=3600 # 接続の再利用時間(秒))知識9: データベースの正規化と非正規化
Section titled “知識9: データベースの正規化と非正規化”データの重複を排除し、整合性を保つ設計手法です。
正規化のレベル:
- 第1正規形(1NF): 繰り返しグループを排除
- 第2正規形(2NF): 部分関数従属を排除
- 第3正規形(3NF): 推移的関数従属を排除
- ボイス・コッド正規形(BCNF): より厳格な正規化
非正規化とは
パフォーマンス向上のために、意図的に正規化を緩める設計手法です。
実装例:
-- 正規化された設計CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(255), email VARCHAR(255));
CREATE TABLE orders ( id INT PRIMARY KEY, user_id INT, total DECIMAL(10,2), FOREIGN KEY (user_id) REFERENCES users(id));
-- 非正規化された設計(パフォーマンス重視)CREATE TABLE orders ( id INT PRIMARY KEY, user_id INT, user_name VARCHAR(255), -- 非正規化: ユーザー名を重複保存 user_email VARCHAR(255), -- 非正規化: メールアドレスを重複保存 total DECIMAL(10,2));
-- メリット:-- 1. JOINが不要で高速-- 2. クエリが簡素化
-- デメリット:-- 1. データの重複-- 2. 更新時の不整合リスク-- 3. ストレージ使用量の増加知識10: データベースのバックアップとリカバリ
Section titled “知識10: データベースのバックアップとリカバリ”バックアップ戦略
Section titled “バックアップ戦略”実装例:
-- MySQL: バックアップmysqldump -u root -p my_database > backup.sql
-- 増分バックアップ(バイナリログを使用)mysqlbinlog --start-datetime="2024-01-01 00:00:00" \ --stop-datetime="2024-01-02 00:00:00" \ mysql-bin.000001 > incremental_backup.sql
-- PostgreSQL: バックアップpg_dump -U postgres my_database > backup.sql
-- ポイントインタイムリカバリ(PITR)-- 1. ベースバックアップpg_basebackup -D /backup/base -Ft -z -P
-- 2. アーカイブログの設定archive_mode = onarchive_command = 'cp %p /backup/archive/%f'6. 実務でのベストプラクティス
Section titled “6. 実務でのベストプラクティス”パターン1: インデックス戦略の決定フロー
Section titled “パターン1: インデックス戦略の決定フロー”flowchart TD A[クエリの分析] --> B{WHERE句のカラムは?} B --> C[等価条件のカラム] B --> D[範囲条件のカラム] C --> E[カーディナリティを確認] D --> E E --> F{カーディナリティは?} F -->|高| G[単独インデックス] F -->|中| H[複合インデックスの検討] F -->|低| I[複合インデックスの後方に配置] G --> J[EXPLAINで確認] H --> J I --> J J --> K{パフォーマンスは良好?} K -->|はい| L[完了] K -->|いいえ| M[インデックスを調整] M --> Jパターン2: インデックスの定期レビュー
Section titled “パターン2: インデックスの定期レビュー”## インデックスの定期レビュー
### レビューの頻度- **月次**: 使用されていないインデックスの確認- **四半期**: パフォーマンスの評価- **年次**: 全体の見直し
### レビューの項目1. **使用されていないインデックス**: 削除を検討2. **重複しているインデックス**: 統合を検討3. **パフォーマンス**: EXPLAINで確認4. **ストレージ使用量**: インデックスサイズの確認5. **更新コスト**: INSERT/UPDATE/DELETEのパフォーマンスパターン3: クエリパフォーマンスの監視
Section titled “パターン3: クエリパフォーマンスの監視”## クエリパフォーマンスの監視
### 監視項目1. **実行時間**: 遅いクエリの特定2. **スキャン行数**: インデックスの効果を確認3. **ロック待ち**: デッドロックの確認4. **リソース使用量**: CPU、メモリ、I/O
### 実装例```sql-- MySQL: 遅いクエリのログSET GLOBAL slow_query_log = 'ON';SET GLOBAL long_query_time = 1; -- 1秒以上
-- PostgreSQL: クエリ統計SELECT query, calls, total_time, mean_time, max_timeFROM pg_stat_statementsORDER BY total_time DESCLIMIT 10;## 7. よくある問題と解決策
### 問題1: インデックスが使用されない
**原因:**- クエリの条件がインデックスと一致しない- 関数を使用している- 型の不一致
**解決策:**```sql-- 問題のあるクエリSELECT * FROM users WHERE UPPER(email) = 'JOHN@EXAMPLE.COM';-- 解決: 関数インデックスを使用CREATE INDEX idx_email_upper ON users((UPPER(email)));
-- 問題のあるクエリSELECT * FROM users WHERE email = 123; -- 型の不一致-- 解決: 型を一致させるSELECT * FROM users WHERE email = '123';問題2: インデックスが多すぎる
Section titled “問題2: インデックスが多すぎる”原因:
- 過度なインデックス作成
- 使用されていないインデックス
解決策:
-- 使用されていないインデックスの確認SELECT schemaname, tablename, indexname, idx_scanFROM pg_stat_user_indexesWHERE idx_scan = 0;
-- 使用されていないインデックスの削除DROP INDEX idx_unused_index;問題3: 複合インデックスの順序が不適切
Section titled “問題3: 複合インデックスの順序が不適切”原因:
- 等価条件と範囲条件の順序が不適切
- カーディナリティを考慮していない
解決策:
-- 良い例: 等価条件を先に、範囲条件を後にCREATE INDEX idx_user_status_created ON orders(user_id, status, created_at);
-- クエリSELECT * FROM ordersWHERE user_id = 123 AND status = 'completed' AND created_at BETWEEN '2024-01-01' AND '2024-12-31';これで、インデックス戦略とその他の重要なデータベース設計知識を理解できるようになりました。