sql-optimization について
データベースプロジェクトのクエリを分析・改善するためのSQL性能最適化スキルです。インデックス、結合、ページネーション、集計などを扱い、実行計画による検証と継続的な監視を重視します。
主な機能
- 01クエリ構造とアンチパターンの分析
- 02インデックス戦略と管理
- 03結合、サブクエリ、集計の最適化
- 04カーソルページネーションと一括操作
- 05実行計画の分析と性能監視
- 06データベース非依存の最適化チェックリスト
活用シーン
- 01遅いクエリと性能上のボトルネックの特定
- 02絞り込みやソートに合わせたインデックス調整
- 03大きなオフセットのページネーションや一括書き込みの改善
- 04実データと実行計画による変更の検証
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ github/ awesome-copilot --skill sql-optimizationインストール後、Claude Code で/sql-optimizationを入力して読み込まれたか確認します。
詳細
SQL パフォーマンス最適化アシスタント
${selection}(選択範囲がない場合はプロジェクト全体)に対する専門的な SQL パフォーマンス最適化。MySQL、PostgreSQL、SQL Server、Oracle などのさまざまな SQL データベースで機能する普遍的な SQL 最適化テクニックに焦点を当てます。
🎯 最適化の主要領域
クエリパフォーマンス分析
-- ❌ BAD: Inefficient query patterns
SELECT * FROM orders o
WHERE YEAR(o.created_at) = 2024
AND o.customer_id IN (
SELECT c.id FROM customers c WHERE c.status = 'active'
);
-- ✅ GOOD: Optimized query with proper indexing hints
SELECT o.id, o.customer_id, o.total_amount, o.created_at
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
WHERE o.created_at >= '2024-01-01'
AND o.created_at < '2025-01-01'
AND c.status = 'active';
-- Required indexes:
-- CREATE INDEX idx_orders_created_at ON orders(created_at);
-- CREATE INDEX idx_customers_status ON customers(status);
-- CREATE INDEX idx_orders_customer_id ON orders(customer_id);
インデックス戦略の最適化
-- ❌ BAD: Poor indexing strategy
CREATE INDEX idx_user_data ON users(email, first_name, last_name, created_at);
-- ✅ GOOD: Optimized composite indexing
-- For queries filtering by email first, then sorting by created_at
CREATE INDEX idx_users_email_created ON users(email, created_at);
-- For full-text name searches
CREATE INDEX idx_users_name ON users(last_name, first_name);
-- For user status queries
CREATE INDEX idx_users_status_created ON users(status, created_at)
WHERE status IS NOT NULL;
サブクエリの最適化
-- ❌ BAD: Correlated subquery
SELECT p.product_name, p.price
FROM products p
WHERE p.price > (
SELECT AVG(price)
FROM products p2
WHERE p2.category_id = p.category_id
);
-- ✅ GOOD: Window function approach
SELECT product_name, price
FROM (
SELECT product_name, price,
AVG(price) OVER (PARTITION BY category_id) as avg_category_price
FROM products
) ranked
WHERE price > avg_category_price;
📊 パフォーマンスチューニングテクニック
JOIN の最適化
-- ❌ BAD: Inefficient JOIN order and conditions
SELECT o.*, c.name, p.product_name
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.id
LEFT JOIN order_items oi ON o.id = oi.order_id
LEFT JOIN products p ON oi.product_id = p.id
WHERE o.created_at > '2024-01-01'
AND c.status = 'active';
-- ✅ GOOD: Optimized JOIN with filtering
SELECT o.id, o.total_amount, c.name, p.product_name
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id AND c.status = 'active'
INNER JOIN order_items oi ON o.id = oi.order_id
INNER JOIN products p ON oi.product_id = p.id
WHERE o.created_at > '2024-01-01';
ページネーションの最適化
-- ❌ BAD: OFFSET-based pagination (slow for large offsets)
SELECT * FROM products
ORDER BY created_at DESC
LIMIT 20 OFFSET 10000;
-- ✅ GOOD: Cursor-based pagination
SELECT * FROM products
WHERE created_at < '2024-06-15 10:30:00'
ORDER BY created_at DESC
LIMIT 20;
-- Or using ID-based cursor
SELECT * FROM products
WHERE id > 1000
ORDER BY id
LIMIT 20;
集計の最適化
-- ❌ BAD: Multiple separate aggregation queries
SELECT COUNT(*) FROM orders WHERE status = 'pending';
SELECT COUNT(*) FROM orders WHERE status = 'shipped';
SELECT COUNT(*) FROM orders WHERE status = 'delivered';
-- ✅ GOOD: Single query with conditional aggregation
SELECT
COUNT(CASE WHEN status = 'pending' THEN 1 END) as pending_count,
COUNT(CASE WHEN status = 'shipped' THEN 1 END) as shipped_count,
COUNT(CASE WHEN status = 'delivered' THEN 1 END) as delivered_count
FROM orders;
🔍 クエリのアンチパターン
SELECT のパフォーマンス問題
-- ❌ BAD: SELECT * anti-pattern
SELECT * FROM large_table lt
JOIN another_table at ON lt.id = at.ref_id;
-- ✅ GOOD: Explicit column selection
SELECT lt.id, lt.name, at.value
FROM large_table lt
JOIN another_table at ON lt.id = at.ref_id;
WHERE 句の最適化
-- ❌ BAD: Function calls in WHERE clause
SELECT * FROM orders
WHERE UPPER(customer_email) = '[email protected]';
-- ✅ GOOD: Index-friendly WHERE clause
SELECT * FROM orders
WHERE customer_email = '[email protected]';
-- Consider: CREATE INDEX idx_orders_email ON orders(LOWER(customer_email));
OR と UNION の最適化
-- ❌ BAD: Complex OR conditions
SELECT * FROM products
WHERE (category = 'electronics' AND price < 1000)
OR (category = 'books' AND price < 50);
-- ✅ GOOD: UNION approach for better optimization
SELECT * FROM products WHERE category = 'electronics' AND price < 1000
UNION ALL
SELECT * FROM products WHERE category = 'books' AND price < 50;
📈 データベース非依存の最適化
バッチ操作
-- ❌ BAD: Row-by-row operations
INSERT INTO products (name, price) VALUES ('Product 1', 10.00);
INSERT INTO products (name, price) VALUES ('Product 2', 15.00);
INSERT INTO products (name, price) VALUES ('Product 3', 20.00);
-- ✅ GOOD: Batch insert
INSERT INTO products (name, price) VALUES
('Product 1', 10.00),
('Product 2', 15.00),
('Product 3', 20.00);
一時テーブルの使用
-- ✅ GOOD: Using temporary tables for complex operations
CREATE TEMPORARY TABLE temp_calculations AS
SELECT customer_id,
SUM(total_amount) as total_spent,
COUNT(*) as order_count
FROM orders
WHERE created_at >= '2024-01-01'
GROUP BY customer_id;
-- Use the temp table for further calculations
SELECT c.name, tc.total_spent, tc.order_count
FROM temp_calculations tc
JOIN customers c ON tc.customer_id = c.id
WHERE tc.total_spent > 1000;
🛠️ インデックス管理
インデックス設計の原則
-- ✅ GOOD: Covering index design
CREATE INDEX idx_orders_covering
ON orders(customer_id, created_at)
INCLUDE (total_amount, status); -- SQL Server syntax
-- Or: CREATE INDEX idx_orders_covering ON orders(customer_id, created_at, total_amount, status); -- Other databases
部分インデックスの戦略
-- ✅ GOOD: Partial indexes for specific conditions
CREATE INDEX idx_orders_active
ON orders(created_at)
WHERE status IN ('pending', 'processing');
📊 パフォーマンス監視クエリ
クエリパフォーマンス分析
-- Generic approach to identify slow queries
-- (Specific syntax varies by database)
-- For MySQL:
SELECT query_time, lock_time, rows_sent, rows_examined, sql_text
FROM mysql.slow_log
ORDER BY query_time DESC;
-- For PostgreSQL:
SELECT query, calls, total_time, mean_time
FROM pg_stat_statements
ORDER BY total_time DESC;
-- For SQL Server:
SELECT
qs.total_elapsed_time/qs.execution_count as avg_elapsed_time,
qs.execution_count,
SUBSTRING(qt.text, (qs.statement_start_offset/2)+1,
((CASE qs.statement_end_offset WHEN -1 THEN DATALENGTH(qt.text)
ELSE qs.statement_end_offset END - qs.statement_start_offset)/2)+1) as query_text
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
ORDER BY avg_elapsed_time DESC;
🎯 普遍的な最適化チェックリスト
クエリ構造
- 本番クエリで SELECT * を避ける
- 適切な JOIN タイプ(INNER と LEFT/RIGHT)を使用する
- WHERE 句で早期にフィルタリングする
- 適切な場合にサブクエリで IN の代わりに EXISTS を使用する
- インデックスの使用を妨げる関数を WHERE 句で使用するのを避ける
インデックス戦略
- 頻繁にクエリされる列にインデックスを作成する
- 複合インデックスを正しい列順で使用する
- 過剰なインデックス作成を避ける(INSERT/UPDATE のパフォーマンスに影響する)
- 効果的な場合はカバリングインデックスを使用する
- 特定のクエリパターンに対して部分インデックスを作成する
データ型とスキーマ
- ストレージ効率のために適切なデータ型を使用する
- 適切に正規化する(OLTP では 3NF、OLAP では非正規化)
- クエリオプティマイザーに役立つ制約を使用する
- 適切な場合に大きなテーブルをパーティション分割する
クエリパターン
- 結果セットの制御に LIMIT/TOP を使用する
- 効率的なページネーション戦略を実装する
- 一括データ変更にバッチ操作を使用する
- N+1 クエリ問題を避ける
- 繰り返し実行されるクエリにプリペアドステートメントを使用する
パフォーマンステスト
- 現実的なデータ量でクエリをテストする
- クエリ実行プランを分析する
- 経時的にクエリパフォーマンスを監視する
- 低速なクエリに対するアラートを設定する
- 定期的なインデックス使用状況の分析
📝 最適化手法
- 特定: データベース固有のツールを使用して低速なクエリを見つける
- 分析: 実行プランを調査し、ボトルネックを特定する
- 最適化: 適切な最適化テクニックを適用する
- テスト: パフォーマンスの改善を検証する
- 監視: パフォーマンスメトリクスを継続的に追跡する
- 反復: 定期的なパフォーマンスレビューと最適化
測定可能なパフォーマンス改善に焦点を当て、常に現実的なデータ量とクエリパターンで最適化をテストしてください。
コメント
Markdown 対応