Appearance
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最佳实践的核心原则包括:
- 设计先行:合理的数据库设计是性能的基础
- 安全第一:遵循最小权限原则,保护数据安全
- 监控到位:持续监控系统性能和健康状况
- 备份保障:建立可靠的备份和恢复机制
- 性能优化:持续优化查询和系统配置
- 高可用性:设计容错和故障恢复机制
通过遵循这些最佳实践,可以构建稳定、高效、安全的MySQL数据库系统,为业务应用提供可靠的数据存储服务。