mcpField
M
mysql-best-practices

mysql-best-practices

github.com/mindrallyデータ・データベース2872.7k 回インストール3 週間前に更新
リポジトリ

mysql-best-practices について

データベース設計者やアプリケーション開発者向けのMySQL開発スキルです。スキーマ設計、クエリとインデックスの最適化、トランザクション、セキュリティ、レプリケーション、保守を扱います。

主な機能

  1. 01スキーマとデータ型の指針
  2. 02インデックスとEXPLAIN分析
  3. 03クエリ最適化の実践
  4. 04トランザクションとレプリケーション
  5. 05セキュリティと保守

活用シーン

  1. 01MySQLのテーブルとインデックス設計
  2. 02遅いクエリや非効率なクエリの調査
  3. 03データベースのセキュリティと保守の確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mindrally/skills --skill mysql-best-practices

インストール後、Claude Code で/mysql-best-practicesを入力して読み込まれたか確認します。

詳細

MySQL のベストプラクティス

基本原則

  • 適切なストレージエンジンを使用してスキーマを設計します(ほとんどの用途では InnoDB)
  • EXPLAIN と適切なインデックスを使用してクエリを最適化します
  • 適切なデータ型を使用して、ストレージを最小限に抑え、パフォーマンスを向上させます
  • 接続プーリングとクエリキャッシュを適切に実装します
  • MySQL 固有のセキュリティ強化のベストプラクティスに従います

スキーマ設計

ストレージエンジンの選択
  • InnoDB をデフォルトのエンジンとして使用します(ACID 準拠、行レベルロック)
  • 読み取り中心でトランザクションを必要としないワークロードに限り、MyISAM を検討します
  • 高速性が求められる一時テーブルには MEMORY エンジンを使用します
CREATE TABLE orders (
    order_id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    customer_id INT UNSIGNED NOT NULL,
    order_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    total_amount DECIMAL(12, 2) NOT NULL,
    status ENUM('pending', 'processing', 'shipped', 'delivered', 'cancelled')
        NOT NULL DEFAULT 'pending',
    INDEX idx_customer (customer_id),
    INDEX idx_date_status (order_date, status),
    FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
        ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
データ型
  • 要件を満たす最小のデータ型を使用します
  • 可能な場合は BIGINT より INT UNSIGNED を優先します
  • 金融計算には FLOAT/DOUBLE ではなく DECIMAL を使用します
  • 値の種類が固定されている場合は ENUM を使用します
  • 可変長文字列には VARCHAR、固定長文字列には CHAR を使用します
  • Unicode を完全にサポートするため、必ず utf8mb4 文字セットを使用します
-- Appropriate data type selection
CREATE TABLE products (
    product_id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    sku VARCHAR(50) NOT NULL,
    name VARCHAR(255) NOT NULL,
    description TEXT,
    price DECIMAL(10, 2) NOT NULL,
    quantity SMALLINT UNSIGNED NOT NULL DEFAULT 0,
    weight DECIMAL(8, 3),
    is_active TINYINT(1) NOT NULL DEFAULT 1,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_sku (sku)
) ENGINE=InnoDB;
主キー
  • InnoDB テーブルでは AUTO_INCREMENT の整数主キーを使用します
  • 分散システムでは、BINARY(16) として保存する UUID を検討します
  • 可能な場合は複合主キーを避けます
-- UUID storage optimization
CREATE TABLE distributed_events (
    event_id BINARY(16) PRIMARY KEY,
    event_type VARCHAR(50) NOT NULL,
    payload JSON,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Insert with UUID
INSERT INTO distributed_events (event_id, event_type, payload)
VALUES (UUID_TO_BIN(UUID()), 'user_signup', '{"user_id": 123}');

-- Query with UUID
SELECT * FROM distributed_events
WHERE event_id = UUID_TO_BIN('550e8400-e29b-41d4-a716-446655440000');

インデックス戦略

インデックスの種類
  • ほとんどのクエリには B-tree インデックス(デフォルト)を使用します
  • テキスト検索には FULLTEXT インデックスを使用します
  • 地理データには SPATIAL インデックスを使用します
  • 頻繁に実行されるクエリにはカバリングインデックスを検討します
-- Composite index for common query patterns
CREATE INDEX idx_orders_customer_date ON orders(customer_id, order_date);

-- Covering index
CREATE INDEX idx_orders_covering ON orders(customer_id, order_date, status, total_amount);

-- Fulltext index for search
ALTER TABLE products ADD FULLTEXT INDEX ft_name_desc (name, description);

-- Search using fulltext
SELECT * FROM products
WHERE MATCH(name, description) AGAINST('wireless bluetooth' IN NATURAL LANGUAGE MODE);
インデックスのガイドライン
  • WHERE、JOIN、ORDER BY、GROUP BY で使用する列にインデックスを設定します
  • 複合インデックスでは、選択性の高い列を先頭に配置します
  • カーディナリティの低い列だけにインデックスを設定するのは避けます
  • 使用されていないインデックスを監視して削除します
-- Check index usage
SELECT
    table_schema, table_name, index_name,
    seq_in_index, column_name, cardinality
FROM information_schema.STATISTICS
WHERE table_schema = 'your_database'
ORDER BY table_name, index_name, seq_in_index;

クエリの最適化

EXPLAIN による分析
  • EXPLAIN を使用してクエリの実行計画を分析します
  • 全テーブルスキャン(type: ALL)がないか確認します
  • インデックスが適切に使用されているか確認します
  • 読み取られた行数と返された行数を監視します
EXPLAIN FORMAT=JSON
SELECT c.name, COUNT(o.order_id) AS order_count
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE c.created_at > '2024-01-01'
GROUP BY c.customer_id;
クエリのベストプラクティス
  • 本番コードでは SELECT * を避けます
  • ページネーションには LIMIT を使用します
  • 可能な場合はサブクエリより JOIN を優先します
  • 繰り返し実行するクエリにはプリペアドステートメントを使用します
-- Efficient pagination
SELECT order_id, order_date, total_amount
FROM orders
WHERE customer_id = ?
ORDER BY order_date DESC
LIMIT 20 OFFSET 0;

-- Keyset pagination (more efficient for large offsets)
SELECT order_id, order_date, total_amount
FROM orders
WHERE customer_id = ?
    AND (order_date, order_id) < (?, ?)
ORDER BY order_date DESC, order_id DESC
LIMIT 20;
よくある落とし穴の回避
-- Avoid: Function on indexed column
SELECT * FROM orders WHERE YEAR(order_date) = 2024;

-- Preferred: Range comparison
SELECT * FROM orders
WHERE order_date >= '2024-01-01' AND order_date < '2025-01-01';

-- Avoid: Implicit type conversion
SELECT * FROM users WHERE user_id = '123';  -- user_id is INT

-- Preferred: Proper types
SELECT * FROM users WHERE user_id = 123;

-- Avoid: LIKE with leading wildcard
SELECT * FROM products WHERE name LIKE '%phone%';

-- Preferred: Fulltext search for text matching
SELECT * FROM products WHERE MATCH(name) AGAINST('phone');

JSON のサポート

  • 半構造化データには JSON データ型を使用します(MySQL 5.7+)
  • 頻繁にアクセスする JSON フィールドには生成列を作成します
  • クエリには適切な JSON 関数を使用します
CREATE TABLE events (
    event_id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    event_type VARCHAR(50) NOT NULL,
    payload JSON NOT NULL,
    -- Generated column for indexing
    user_id INT UNSIGNED AS (payload->>'$.user_id') STORED,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_user_id (user_id)
);

-- Query JSON data
SELECT event_id, event_type,
       JSON_EXTRACT(payload, '$.action') AS action
FROM events
WHERE JSON_EXTRACT(payload, '$.user_id') = 123;

-- Or using -> operator
SELECT * FROM events WHERE payload->'$.user_id' = 123;

トランザクション管理

  • トランザクションを使用するテーブルには InnoDB を使用します
  • ロック競合を最小限に抑えるため、トランザクションを短く保ちます
  • 適切な分離レベルを選択します
  • デッドロックを適切に処理します
-- Transaction with error handling
START TRANSACTION;

UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;

-- Check for errors and commit or rollback
COMMIT;

-- Set isolation level
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

レプリケーションと高可用性

読み取りレプリカ
  • 読み取りクエリをレプリカに送ります
  • 読み取り/書き込みを分離する接続プーリングを使用します
  • レプリケーション遅延を監視します
-- Check replication status
SHOW SLAVE STATUS\G

-- Check replication lag
SELECT TIMESTAMPDIFF(SECOND,
    MAX(LAST_APPLIED_TRANSACTION_END_APPLY_TIMESTAMP),
    NOW()) AS lag_seconds
FROM performance_schema.replication_applier_status_by_worker;

セキュリティ

  • 強力なパスワードと安全な接続(SSL/TLS)を使用します
  • 最小権限の原則を適用します
  • SQL インジェクションを防ぐため、プリペアドステートメントを使用します
  • 機密性の高い操作を監査します
-- Create user with limited privileges
CREATE USER 'app_user'@'%' IDENTIFIED BY 'secure_password';
GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'app_user'@'%';
FLUSH PRIVILEGES;

-- Require SSL
ALTER USER 'app_user'@'%' REQUIRE SSL;

-- View user privileges
SHOW GRANTS FOR 'app_user'@'%';

メンテナンス

定期的なメンテナンス作業
-- Analyze tables for optimizer statistics
ANALYZE TABLE orders, customers, products;

-- Optimize tables (reclaim space, defragment)
OPTIMIZE TABLE orders;

-- Check table integrity
CHECK TABLE orders;
クエリの監視
-- Find slow queries
SELECT * FROM mysql.slow_log ORDER BY query_time DESC LIMIT 10;

-- Current process list
SHOW FULL PROCESSLIST;

-- InnoDB status
SHOW ENGINE INNODB STATUS;

-- Table sizes
SELECT
    table_name,
    ROUND(data_length / 1024 / 1024, 2) AS data_mb,
    ROUND(index_length / 1024 / 1024, 2) AS index_mb,
    table_rows
FROM information_schema.TABLES
WHERE table_schema = 'your_database'
ORDER BY data_length DESC;

推奨設定

# my.cnf recommended settings

[mysqld]
# InnoDB settings
innodb_buffer_pool_size = 70%_of_RAM
innodb_log_file_size = 256M
innodb_flush_log_at_trx_commit = 1
innodb_flush_method = O_DIRECT

# Connection settings
max_connections = 500
wait_timeout = 300
interactive_timeout = 300

# Query cache (disabled in MySQL 8.0+)
query_cache_type = 0

# Slow query log
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2

コメント