Skip to content
On this page

MySQL最佳实践

MySQL最佳实践涵盖了数据库设计、开发、运维等各个方面的经验总结,旨在帮助开发者和DBA构建高性能、高可用、可维护的MySQL应用系统。

数据库设计最佳实践

表结构设计

选择合适的数据类型

sql
-- 选择最小的数据类型以节省空间
-- 不好的做法
CREATE TABLE users (
    id BIGINT,           -- 如果ID不会超过INT范围,使用INT即可
    age VARCHAR(10),     -- 年龄用TINYINT即可
    status VARCHAR(255)  -- 状态用ENUM或TINYINT更合适
);

-- 好的做法
CREATE TABLE users (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,  -- UNSIGNED节省空间
    age TINYINT UNSIGNED NOT NULL,              -- 精确的数据类型
    status ENUM('active', 'inactive', 'suspended') NOT NULL DEFAULT 'active',  -- 枚举类型
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

主键设计

sql
-- 推荐使用自增整数主键
CREATE TABLE orders (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id INT UNSIGNED NOT NULL,
    order_no VARCHAR(32) NOT NULL UNIQUE,
    amount DECIMAL(10,2) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    INDEX idx_user_id (user_id),
    INDEX idx_created_at (created_at)
);

-- 避免使用复合主键作为业务主键
-- 不推荐
-- CREATE TABLE order_items (
--     order_id INT,
--     product_id INT,
--     quantity INT,
--     PRIMARY KEY (order_id, product_id)  -- 业务主键
-- );

-- 推荐
CREATE TABLE order_items (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    order_id INT UNSIGNED NOT NULL,
    product_id INT UNSIGNED NOT NULL,
    quantity INT NOT NULL,
    
    UNIQUE KEY uk_order_product (order_id, product_id),
    INDEX idx_order_id (order_id),
    INDEX idx_product_id (product_id)
);

字符集和排序规则

sql
-- 统一使用UTF8MB4字符集
CREATE DATABASE myapp 
CHARACTER SET utf8mb4 
COLLATE utf8mb4_unicode_ci;

CREATE TABLE users (
    id INT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100) NOT NULL
) 
CHARACTER SET utf8mb4 
COLLATE utf8mb4_unicode_ci;

索引最佳实践

索引设计原则

sql
-- 1. 为经常查询的列创建索引
-- 2. 为JOIN条件创建索引
-- 3. 为ORDER BY和GROUP BY创建索引
-- 4. 优先为高选择性的列创建索引

-- 示例:电商订单表索引设计
CREATE TABLE orders (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id INT UNSIGNED NOT NULL,
    order_no VARCHAR(32) NOT NULL UNIQUE,
    status TINYINT NOT NULL,
    total_amount DECIMAL(10,2) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- 创建索引
CREATE INDEX idx_user_status_created ON orders(user_id, status, created_at);  -- 复合索引
CREATE INDEX idx_status ON orders(status);  -- 状态查询
CREATE INDEX idx_created_at ON orders(created_at);  -- 时间范围查询

复合索引优化

sql
-- 复合索引列顺序原则:
-- 1. 等值查询列优先
-- 2. 高选择性列优先
-- 3. 高频查询列优先

-- 好的复合索引设计
CREATE INDEX idx_status_user_date ON orders(status, user_id, created_at);

-- 支持的查询模式
-- WHERE status = ? 
-- WHERE status = ? AND user_id = ?
-- WHERE status = ? AND user_id = ? AND created_at > ?

-- 不支持的查询模式
-- WHERE user_id = ?                    -- 跳过了最左列
-- WHERE created_at > ?                 -- 跳过了最左列
-- WHERE user_id = ? AND created_at > ? -- 跳过了中间列

SQL查询最佳实践

查询优化

sql
-- 避免在WHERE子句中使用函数
-- 不好的做法
SELECT * FROM users WHERE YEAR(created_at) = 2023;

-- 好的做法
SELECT * FROM users 
WHERE created_at >= '2023-01-01' 
AND created_at < '2024-01-01';

-- 避免SELECT *
-- 不好的做法
SELECT * FROM users WHERE id = 1;

-- 好的做法
SELECT id, username, email FROM users WHERE id = 1;

-- 使用EXISTS替代IN(在某些情况下)
-- 不好的做法
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE status = 'pending');

-- 好的做法
SELECT * FROM users u 
WHERE EXISTS (
    SELECT 1 FROM orders o 
    WHERE o.user_id = u.id AND o.status = 'pending'
);

分页查询优化

sql
-- 避免OFFSET深分页问题
-- 不好的做法
SELECT * FROM orders ORDER BY id LIMIT 100000, 10;  -- 深分页性能差

-- 好的做法:使用游标分页
SELECT * FROM orders WHERE id > 100000 ORDER BY id LIMIT 10;

-- 使用延迟关联
SELECT o.* FROM orders o
INNER JOIN (
    SELECT id FROM orders ORDER BY id LIMIT 100000, 10
) AS lim ON o.id = lim.id;

事务管理最佳实践

事务设计

sql
DELIMITER //
CREATE PROCEDURE TransferMoney(
    IN p_from_user INT,
    IN p_to_user INT,
    IN p_amount DECIMAL(10,2)
)
BEGIN
    DECLARE v_from_balance DECIMAL(10,2);
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK;
        RESIGNAL;
    END;

    START TRANSACTION;

    -- 使用FOR UPDATE锁定记录
    SELECT balance INTO v_from_balance 
    FROM accounts 
    WHERE user_id = p_from_user 
    FOR UPDATE;

    IF v_from_balance < p_amount THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '余额不足';
    END IF;

    -- 执行转账
    UPDATE accounts SET balance = balance - p_amount WHERE user_id = p_from_user;
    UPDATE accounts SET balance = balance + p_amount WHERE user_id = p_to_user;

    -- 记录交易日志
    INSERT INTO transactions (from_user, to_user, amount, created_at)
    VALUES (p_from_user, p_to_user, p_amount, NOW());

    COMMIT;
END //
DELIMITER ;

事务范围控制

sql
-- 保持事务尽可能短
-- 不好的做法:长时间运行的事务
START TRANSACTION;
SELECT * FROM large_table; -- 处理大量数据
-- 长时间计算
UPDATE accounts SET balance = new_balance WHERE id = 1;
COMMIT;

-- 好的做法:缩短事务范围
SELECT * FROM large_table; -- 在事务外处理数据
START TRANSACTION;
UPDATE accounts SET balance = calculated_balance WHERE id = 1;
COMMIT;

配置优化最佳实践

关键配置参数

ini
# my.cnf - MySQL配置文件最佳实践

[mysqld]
# 基本设置
port = 3306
socket = /var/run/mysqld/mysqld.sock

# 内存配置 - 最重要的优化参数
innodb_buffer_pool_size = 70%  # 物理内存的70%,如果是专用数据库服务器
innodb_log_file_size = 256M    # 事务日志大小,影响恢复时间和写性能
innodb_log_buffer_size = 16M   # 日志缓冲区

# 连接配置
max_connections = 500          # 根据应用需求调整
wait_timeout = 28800           # 连接超时时间
interactive_timeout = 28800    # 交互式连接超时时间
thread_cache_size = 16         # 线程缓存大小

# InnoDB配置
innodb_file_per_table = 1      # 每个表独立表空间
innodb_flush_log_at_trx_commit = 2  # 性能和安全的平衡点
innodb_lock_wait_timeout = 50  # 锁等待超时时间
innodb_thread_concurrency = 0  # 0表示自动调节

# 查询缓存(MySQL 8.0已移除)
# query_cache_type = 1
# query_cache_size = 64M

# 日志配置
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = 1

# 其他优化
sql_mode = "STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO"

安全最佳实践

用户权限管理

sql
-- 遵循最小权限原则
-- 应用程序用户
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT, UPDATE, DELETE ON myapp.* TO 'app_user'@'localhost';

-- 只读用户
CREATE USER 'readonly_user'@'%' IDENTIFIED BY 'readonly_password';
GRANT SELECT ON myapp.* TO 'readonly_user'@'%';

-- 备份用户
CREATE USER 'backup_user'@'localhost' IDENTIFIED BY 'backup_password';
GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER ON *.* TO 'backup_user'@'localhost';

-- 监控用户
CREATE USER 'monitor_user'@'%' IDENTIFIED BY 'monitor_password';
GRANT PROCESS, REPLICATION CLIENT ON *.* TO 'monitor_user'@'%';
FLUSH PRIVILEGES;

密码安全

sql
-- 启用密码验证插件(MySQL 8.0+)
INSTALL COMPONENT 'file://component_validate_password';

SET GLOBAL validate_password.policy = MEDIUM;
SET GLOBAL validate_password.length = 12;

-- 定期更新密码
ALTER USER 'app_user'@'localhost' IDENTIFIED BY 'new_strong_password';

-- 设置密码过期策略
ALTER USER 'app_user'@'localhost' PASSWORD EXPIRE INTERVAL 90 DAY;

备份最佳实践

备份策略

bash
#!/bin/bash
# 生产环境MySQL备份脚本

BACKUP_DIR="/backup/mysql"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=30
MYSQL_USER="backup_user"
MYSQL_PASSWORD="backup_password"

# 创建备份目录
mkdir -p $BACKUP_DIR

# 完整备份
mysqldump -u $MYSQL_USER -p$MYSQL_PASSWORD \
  --single-transaction \
  --routines \
  --triggers \
  --all-databases \
  --master-data=2 | gzip > $BACKUP_DIR/full_backup_$DATE.sql.gz

# 验证备份完整性
if gunzip --test $BACKUP_DIR/full_backup_$DATE.sql.gz; then
  echo "备份验证成功: full_backup_$DATE.sql.gz"
  
  # 删除过期备份
  find $BACKUP_DIR -name "full_backup_*.sql.gz" -mtime +$RETENTION_DAYS -delete
  
  # 同步到远程备份服务器
  rsync -avz $BACKUP_DIR/full_backup_$DATE.sql.gz user@remote_server:/backup/
else
  echo "备份验证失败,发送告警邮件"
  echo "备份失败" | mail -s "MySQL Backup Failed" admin@company.com
  exit 1
fi

二进制日志管理

sql
-- 启用二进制日志(在my.cnf中配置)
-- [mysqld]
-- log-bin=mysql-bin
-- expire_logs_days=7
-- max_binlog_size=100M
-- binlog_format=ROW

-- 查看二进制日志配置
SHOW VARIABLES LIKE 'log_bin%';
SHOW VARIABLES LIKE 'expire_logs_days';
SHOW MASTER LOGS;

监控最佳实践

关键监控指标

sql
-- 监控连接数
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Threads_running';

-- 监控查询性能
SHOW STATUS LIKE 'Questions';  -- 总查询数
SHOW STATUS LIKE 'Slow_queries';  -- 慢查询数

-- 监控InnoDB状态
SHOW ENGINE INNODB STATUS\G

-- 使用Performance Schema监控
SELECT 
    EVENT_NAME,
    COUNT_STAR,
    SUM_TIMER_WAIT/1000000000 AS TOTAL_WAIT_S
FROM performance_schema.events_waits_summary_global_by_event_name
WHERE COUNT_STAR > 0
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;

使用sys模式监控

sql
-- 查看最慢的查询
SELECT * FROM sys.statement_analysis 
ORDER BY total_latency DESC 
LIMIT 10;

-- 查看全表扫描的查询
SELECT * FROM sys.statements_with_full_table_scans 
WHERE exec_count > 10;

-- 查看表统计信息
SELECT * FROM sys.schema_table_statistics 
ORDER BY rows_fetched DESC;

高可用性最佳实践

主从复制配置

sql
-- 主库配置 (my.cnf)
# [mysqld]
# server-id=1
# log-bin=mysql-bin
# binlog-format=ROW
# binlog-do-db=myapp

-- 从库配置 (my.cnf)
# [mysqld]
# server-id=2
# relay-log=relay-bin
# read_only=1
# replicate-do-db=myapp

-- 设置主从复制
-- 在主库上
GRANT REPLICATION SLAVE ON *.* TO 'repl_user'@'%' IDENTIFIED BY 'repl_password';

-- 在从库上
CHANGE MASTER TO
  MASTER_HOST='master_host',
  MASTER_USER='repl_user',
  MASTER_PASSWORD='repl_password',
  MASTER_LOG_FILE='mysql-bin.000001',
  MASTER_LOG_POS=154;

START SLAVE;
SHOW SLAVE STATUS\G

读写分离

javascript
// Node.js应用中的读写分离示例
class DatabaseRouter {
  constructor() {
    this.writePool = mysql.createPool(writeConfig);
    this.readPool = mysql.createPool(readConfig);
  }

  async query(sql, params, isWrite = false) {
    const pool = isWrite ? this.writePool : this.readPool;
    const [rows] = await pool.execute(sql, params);
    return rows;
  }

  async writeQuery(sql, params) {
    return this.query(sql, params, true);
  }

  async readQuery(sql, params) {
    return this.query(sql, params, false);
  }
}

性能调优最佳实践

查询优化检查清单

sql
-- 1. 检查是否有合适的索引
EXPLAIN SELECT * FROM users WHERE email = 'user@example.com';

-- 2. 避免SELECT *
SELECT id, username, email FROM users WHERE id = 1;

-- 3. 使用LIMIT限制结果集
SELECT * FROM large_table WHERE condition LIMIT 100;

-- 4. 优化JOIN操作
-- 确保JOIN条件上有索引
-- 避免笛卡尔积

-- 5. 使用批量操作
INSERT INTO users (username, email) VALUES 
('user1', 'user1@example.com'),
('user2', 'user2@example.com'),
('user3', 'user3@example.com');

表优化

sql
-- 定期分析表统计信息
ANALYZE TABLE users, orders;

-- 优化表(整理碎片)
OPTIMIZE TABLE large_table;

-- 检查表完整性
CHECK TABLE users;

-- 使用分区表处理大数据量
CREATE TABLE sales (
    id INT AUTO_INCREMENT,
    sale_date DATE NOT NULL,
    amount DECIMAL(10,2),
    PRIMARY KEY (id, sale_date)
)
PARTITION BY RANGE (YEAR(sale_date)) (
    PARTITION p2022 VALUES LESS THAN (2023),
    PARTITION p2023 VALUES LESS THAN (2024),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

应用开发最佳实践

连接池配置

javascript
// 连接池最佳配置
const mysql = require('mysql2/promise');

const pool = mysql.createPool({
  host: 'localhost',
  user: 'app_user',
  password: process.env.DB_PASSWORD,
  database: 'myapp',
  
  // 连接池大小
  connectionLimit: 20,      // 根据应用并发需求调整
  queueLimit: 0,            // 0表示无限制
  acquireTimeout: 60000,    // 获取连接超时时间
  timeout: 60000,           // 查询超时时间
  
  // 连接健康检查
  reconnect: true,
  enableKeepAlive: true,
  keepAliveInitialDelay: 0,
});

错误处理

javascript
class RobustDatabaseService {
  async executeWithRetry(query, params, maxRetries = 3) {
    let lastError;
    
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        return await pool.execute(query, params);
      } catch (error) {
        lastError = error;
        
        // 对于可重试的错误进行重试
        if (this.isRetryableError(error) && attempt < maxRetries) {
          await this.delay(1000 * Math.pow(2, attempt - 1)); // 指数退避
          continue;
        }
        
        break;
      }
    }
    
    throw lastError;
  }

  isRetryableError(error) {
    return [
      'PROTOCOL_CONNECTION_LOST',
      'ECONNRESET',
      'ETIMEDOUT',
      'EPIPE',
      1213,  // Deadlock
      1205   // Lock wait timeout
    ].includes(error.code || error.errno);
  }
}

维护最佳实践

定期维护任务

sql
-- 每周执行
ANALYZE TABLE users, orders, products;

-- 每月执行
OPTIMIZE TABLE large_log_table;

-- 检查表损坏
CHECK TABLE important_table;

-- 清理过期数据
DELETE FROM logs WHERE created_at < DATE_SUB(NOW(), INTERVAL 3 MONTH);

性能基准测试

bash
# 定期性能测试脚本
mysqlslap --concurrency=1,50,100 --iterations=3 \
  --create-schema=test_db \
  --query="SELECT * FROM users WHERE id=1" \
  --commit=100 \
  --engine=innodb \
  --number-of-queries=1000

总结

MySQL最佳实践的核心原则包括:

  1. 设计先行:合理的数据库设计是性能的基础
  2. 安全第一:遵循最小权限原则,保护数据安全
  3. 监控到位:持续监控系统性能和健康状况
  4. 备份保障:建立可靠的备份和恢复机制
  5. 性能优化:持续优化查询和系统配置
  6. 高可用性:设计容错和故障恢复机制

通过遵循这些最佳实践,可以构建稳定、高效、安全的MySQL数据库系统,为业务应用提供可靠的数据存储服务。