外观
MySQL 经典故障案例分析
案例一:慢查询导致数据库服务器负载过高
故障现象
- 数据库服务器 CPU 使用率持续高达 99%
- 应用响应时间延长,部分请求超时
- 连接数急剧增加,接近最大连接数限制
故障原因分析
- 查看当前连接状态
sql
SHOW FULL PROCESSLIST;发现大量相同的 SELECT 查询处于 "Sending data" 状态。
- 分析慢查询日志
查看慢查询日志,发现一条复杂的 JOIN 查询执行时间超过 30 秒,且被频繁执行。
- 查看执行计划
sql
EXPLAIN SELECT * FROM orders o
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id
WHERE o.created_at BETWEEN '2023-01-01' AND '2023-01-31'
AND p.category_id = 1;发现查询未使用索引,进行了全表扫描。
解决方案
- 优化查询语句
- 只选择需要的列,避免使用
SELECT * - 添加适当的索引:
ALTER TABLE orders ADD INDEX idx_created_at (created_at); - 优化 JOIN 条件
- 调整数据库参数
- 增加
innodb_buffer_pool_size,提高缓存命中率 - 调整
max_connections,避免连接数耗尽
- 应用层优化
- 减少查询频率,添加缓存机制
- 优化应用逻辑,避免不必要的查询
经验教训
- 定期分析慢查询日志,及时发现和优化慢查询
- 为经常查询的列添加适当的索引
- 避免在生产环境中使用复杂的 JOIN 查询
- 应用层添加缓存机制,减少数据库压力
案例二:主从复制延迟过大
故障现象
- 主从复制延迟超过 1 小时
- 从库无法及时同步主库数据
- 依赖从库的业务系统出现数据不一致问题
故障原因分析
- 查看复制状态
sql
SHOW SLAVE STATUS\G;发现从库的 Seconds_Behind_Master 为 3600 秒。
- 检查从库状态
- 从库 CPU 使用率接近 100%
- 从库 I/O 等待时间过长
- 从库存在大量慢查询
- 分析二进制日志
发现主库执行了大量的批量更新操作,生成了大量的二进制日志。
解决方案
- 优化从库配置
- 增加从库的硬件资源(CPU、内存、磁盘)
- 调整
innodb_buffer_pool_size,提高缓存命中率 - 开启
slave_parallel_workers,启用并行复制
- 优化主库操作
- 避免在业务高峰期执行大量批量操作
- 拆分大的批量更新为多个小批量更新
- 优化 SQL 语句,减少二进制日志量
- 监控和告警
- 设置主从延迟告警阈值
- 定期检查复制状态
- 配置自动切换机制
经验教训
- 主从复制需要考虑硬件资源匹配
- 启用并行复制可以提高复制效率
- 避免在业务高峰期执行大量批量操作
- 建立完善的监控和告警机制
案例三:数据库连接数耗尽
故障现象
- 应用无法连接到数据库,报错 "Too many connections"
- 数据库服务器连接数达到最大限制
- 业务系统完全不可用
故障原因分析
- 查看当前连接数
sql
SHOW GLOBAL STATUS LIKE 'Threads_connected';
SHOW VARIABLES LIKE 'max_connections';发现连接数已达到 max_connections 限制(默认 151)。
- 分析连接来源
查看 processlist,发现大量来自应用服务器的连接处于 "Sleep" 状态,长时间未释放。
- 检查应用代码
发现应用程序存在连接泄漏问题,未正确关闭数据库连接。
解决方案
- 临时增加连接数
sql
SET GLOBAL max_connections = 1000;- 优化连接池配置
- 调整连接池的最大连接数
- 设置合理的连接超时时间
- 配置连接池的心跳机制
- 修复应用代码
- 确保所有数据库连接都能正确关闭
- 使用 try-with-resources 或 finally 块关闭连接
- 增加连接使用日志,便于排查连接泄漏
- 优化数据库配置
- 设置
wait_timeout和interactive_timeout,自动关闭空闲连接 - 启用连接池监控,及时发现连接泄漏
经验教训
- 合理设置连接池参数,避免连接数过多
- 确保应用程序正确关闭数据库连接
- 定期监控连接使用情况
- 启用连接超时机制,自动关闭空闲连接
案例四:磁盘空间不足导致数据库崩溃
故障现象
- 数据库突然崩溃,无法启动
- 错误日志显示 "Error writing file '/var/lib/mysql/binlog.000001' (Errcode: 28 - No space left on device)"
- 磁盘使用率 100%
故障原因分析
- 检查磁盘空间
bash
df -h发现 /var/lib/mysql 所在分区磁盘空间已耗尽。
- 分析磁盘使用情况
bash
du -sh /var/lib/mysql/*发现二进制日志文件占用了大量空间,最大的二进制日志文件达到 100GB。
- 查看二进制日志配置
sql
SHOW VARIABLES LIKE 'expire_logs_days';
SHOW VARIABLES LIKE 'max_binlog_size';发现 expire_logs_days 未设置(默认 0,即不自动清理),max_binlog_size 设置为 1GB。
解决方案
- 清理磁盘空间
- 删除过期的二进制日志文件
- 调整二进制日志保留策略
- 重启数据库
bash
systemctl start mysql- 优化二进制日志配置
sql
SET GLOBAL expire_logs_days = 7;
SET GLOBAL max_binlog_size = 500M;- 定期监控磁盘空间
- 设置磁盘空间告警阈值
- 定期清理不必要的日志文件
- 考虑使用外部存储存储二进制日志
经验教训
- 定期检查磁盘空间使用情况
- 设置合理的二进制日志保留策略
- 启用自动清理过期日志
- 考虑使用外部存储存储日志文件
案例五:死锁导致事务回滚
故障现象
- 应用程序频繁报 "Deadlock found when trying to get lock; try restarting transaction"
- 事务执行失败,需要重新执行
- 系统吞吐量下降
故障原因分析
- 查看死锁日志
在 MySQL 5.6+ 中,可以通过 SHOW ENGINE INNODB STATUS 查看最近的死锁信息。
- 分析死锁原因
发现两个事务互相等待对方释放锁:
- 事务 A:更新表 A 的行 1,然后更新表 B 的行 1
- 事务 B:更新表 B 的行 1,然后更新表 A 的行 1
- 查看事务隔离级别
sql
SHOW VARIABLES LIKE 'transaction_isolation';发现使用的是默认的 REPEATABLE-READ 隔离级别。
解决方案
- 优化事务顺序
- 确保所有事务按照相同的顺序访问表和行
- 简化事务,减少事务持有锁的时间
- 调整事务隔离级别
- 考虑使用
READ-COMMITTED隔离级别,减少锁的持有时间 - 或使用
SERIALIZABLE隔离级别,避免幻读
- 添加适当的索引
- 为经常更新的列添加索引,减少锁的范围
- 避免全表扫描,减少锁的冲突
- 应用层处理
- 实现重试机制,自动重试死锁的事务
- 优化应用逻辑,减少并发更新
经验教训
- 确保所有事务按照相同的顺序访问资源
- 简化事务,减少事务持有锁的时间
- 为经常更新的列添加适当的索引
- 实现重试机制,处理死锁情况
版本差异对故障处理的影响
MySQL 5.6 及之前版本
- 死锁信息只能通过
SHOW ENGINE INNODB STATUS查看,且只显示最近一次死锁 - 不支持并行复制,主从延迟问题较难解决
- Performance Schema 功能有限,监控能力较弱
- 慢查询日志格式较为简单,缺少详细的执行计划信息
MySQL 5.7 版本
- 引入了
performance_schema.events_transactions_*表,增强了事务监控 - 支持并行复制(基于库的并行复制)
- 改进了慢查询日志格式,包含更多执行计划信息
- 引入了
innodb_deadlock_detect变量,可禁用死锁检测
MySQL 8.0 版本
- 支持基于写集合的并行复制,大幅提高复制效率
- 引入了
innodb_undo_log_truncate功能,自动清理 undo 日志 - 增强了 Performance Schema,提供更详细的性能监控
- 支持
EXPLAIN ANALYZE,提供更详细的执行计划分析 - 引入了
SET PERSIST命令,无需重启即可永久修改变量
生产实践建议
1. 建立完善的监控体系
- 监控 CPU、内存、磁盘、网络等系统资源
- 监控连接数、查询响应时间、慢查询数量等数据库指标
- 监控主从复制延迟、二进制日志大小等复制指标
2. 定期进行性能优化
- 定期分析慢查询日志,优化查询语句
- 定期检查索引使用情况,删除冗余索引
- 定期优化表结构,整理碎片
3. 建立故障处理流程
- 制定详细的故障处理流程
- 定期进行故障演练,提高团队应对能力
- 建立故障知识库,总结经验教训
4. 合理配置数据库参数
- 根据硬件资源和业务需求,合理配置数据库参数
- 定期调整参数,优化数据库性能
- 记录参数调整历史,便于回溯
常见问题(FAQ)
Q1: 如何快速定位 MySQL 故障?
A1: 可以通过以下步骤快速定位故障:
- 查看系统资源使用情况
- 查看数据库连接状态
- 分析慢查询日志
- 查看错误日志
- 查看 InnoDB 状态
Q2: 如何避免慢查询导致的性能问题?
A2: 可以通过以下方法避免慢查询:
- 为经常查询的列添加适当的索引
- 优化查询语句,避免复杂的 JOIN 查询
- 应用层添加缓存机制,减少数据库压力
- 定期分析慢查询日志,及时优化
Q3: 如何解决主从复制延迟问题?
A3: 可以通过以下方法解决主从复制延迟:
- 启用并行复制
- 优化主库操作,避免批量更新
- 增加从库的硬件资源
- 调整从库配置参数
Q4: 如何处理数据库连接数耗尽问题?
A4: 可以通过以下方法处理连接数耗尽:
- 临时增加
max_connections参数 - 优化连接池配置,设置合理的连接超时时间
- 修复应用代码,确保正确关闭连接
- 启用连接超时机制,自动关闭空闲连接
Q5: 如何避免死锁问题?
A5: 可以通过以下方法避免死锁:
- 确保所有事务按照相同的顺序访问资源
- 简化事务,减少事务持有锁的时间
- 为经常更新的列添加适当的索引
- 调整事务隔离级别
- 实现重试机制,处理死锁情况
