
凌晨2点,手机铃声突然响起——"MySQL数据库卡死了!所有业务都不可用!"这是每个运维工程师最不想接到的电话。生产环境的MySQL突然卡死,不仅会造成业务中断,还可能导致数据丢失和客户投诉。经历过多次生产事故后,我总结出了一套行之有效的5步排查法,帮助我在最短时间内定位并解决问题。本文将详细分享这套实战方法,让你在遇到类似问题时能够快速响应、准确定位。
MySQL"卡死"并不是一个准确的技术术语,它通常指数据库响应极其缓慢或完全无响应的状态。从技术角度来看,这种现象可能由多种原因引起:
理解MySQL的线程状态对于排查问题至关重要。每个客户端连接在MySQL内部对应一个线程,这些线程可能处于不同的状态:
当大量线程长时间处于某个非正常状态时,就会表现为"卡死"。
2023年双11期间,我们的电商系统在流量高峰期突然出现MySQL卡死。监控显示连接数在10秒内从200飙升到2000(上限),所有API请求超时。最终排查发现是一个未加索引的关联查询在大表上执行,导致表锁持续时间过长,引发雪崩效应。这次事故让我深刻认识到系统性排查方法的重要性。
目标:在30秒内确认MySQL进程是否还活着,基本资源消耗如何。
# 检查MySQL进程状态
ps aux | grep mysqld
# 查看MySQL进程的资源占用
top -p $(pidof mysqld)
# 快速查看系统负载
uptime
# 检查磁盘空间
df -h
某次故障中,我发现MySQL进程CPU占用率只有5%,但load average高达50。这个反常现象提示问题不在CPU计算上,而是大量线程在等待某种资源(后来证实是表锁)。
目标:找出正在执行的SQL和阻塞情况。
-- 查看当前所有连接
SHOWFULL PROCESSLIST;
-- 查看当前运行时间超过2秒的线程
SELECT*FROM information_schema.PROCESSLIST
WHERE COMMAND !='Sleep'ANDTIME>2
ORDERBYTIMEDESC;
-- 统计各状态的线程数量
SELECT STATE, COUNT(*) as count
FROM information_schema.PROCESSLIST
GROUPBY STATE
ORDERBY count DESC;
SHOW PROCESSLIST关键字段:
危险信号:
Locked或Waiting for table metadata lock状态我习惯在终端开两个窗口,一个持续执行SHOW PROCESSLIST,另一个用于分析和执行KILL操作。这样可以实时观察线程状态变化,快速判断处理效果。
# 在一个窗口持续监控
watch -n 2 'mysql -e "SHOW PROCESSLIST\G" | grep -E "(Id|User|State|Time|Info)"'
目标:识别锁冲突,找到持有锁的源头线程。
-- 查看当前锁等待情况(MySQL 5.7+)
SELECT
r.trx_id AS waiting_trx_id,
r.trx_mysql_thread_id AS waiting_thread,
r.trx_query AS waiting_query,
b.trx_id AS blocking_trx_id,
b.trx_mysql_thread_id AS blocking_thread,
b.trx_query AS blocking_query
FROM information_schema.innodb_lock_waits w
INNERJOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id
INNERJOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id;
-- MySQL 8.0+ 新版本使用
SELECT*FROM performance_schema.data_locks;
SELECT*FROM performance_schema.data_lock_waits;
-- 查看死锁日志
SHOW ENGINE INNODB STATUS\G
元数据锁常被忽视,但它是导致"卡死"的常见原因,尤其是在执行DDL操作时。
-- 查看元数据锁等待(MySQL 5.7+)
SELECT
locked_table_schema,
locked_table_name,
locked_type,
waiting_processlist_id,
waiting_query,
blocking_processlist_id
FROM sys.schema_table_lock_waits;
场景1:未提交事务持有锁
某次故障中,一个开发人员在测试环境执行了BEGIN; UPDATE users SET status=1;后忘记提交,连接保持在Sleep状态。随后的业务更新操作全部被阻塞。
解决方法:
-- 找到Sleep状态但有未提交事务的连接
SELECT*FROM information_schema.innodb_trx WHERE trx_state ='RUNNING';
KILL <thread_id>;
场景2:大表DDL引发元数据锁
在线上执行ALTER TABLE时,如果有长时间运行的查询未结束,会导致DDL被阻塞,进而阻塞所有后续的该表访问。
预防措施:
目标:识别资源消耗型查询和系统瓶颈。
# 使用pt-query-digest分析慢查询日志
pt-query-digest /var/log/mysql/slow.log | head -n 100
# 或使用mysqldumpslow
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
-- 查看当前系统资源使用
SHOWGLOBAL STATUS LIKE'%thread%';
SHOWGLOBAL STATUS LIKE'%connection%';
-- 查看InnoDB状态
SHOW ENGINE INNODB STATUS\G
-- 重点关注以下指标:
-- 1. Threads_connected vs max_connections
-- 2. Threads_running (正在执行的线程数,超过CPU核心数2倍需警惕)
-- 3. Innodb_row_lock_waits (行锁等待次数)
-- 4. Innodb_buffer_pool_wait_free (等待空闲页的次数)
# I/O性能检查
iostat -x 1 10
# 关注指标:
# - %util 接近100%表示I/O饱和
# - await 平均等待时间,SSD应<5ms,HDD应<20ms
# 查看MySQL相关的I/O情况
iotop -p $(pidof mysqld)
# 内存检查
free -h
cat /proc/$(pidof mysqld)/status | grep -E '(VmSize|VmRSS)'
曾遇到一个案例,processlist显示大量Sending data状态,但单个查询并不慢。最终发现是网络带宽打满——业务同时导出大量数据,导致网络成为瓶颈。这提醒我们排查要全面,不能只盯着数据库本身。
目标:快速恢复业务,避免二次伤害。
1. KILL阻塞线程
-- 批量KILL某个用户的连接
SELECT CONCAT('KILL ',id,';') FROM information_schema.PROCESSLIST
WHEREUSER='webapp'ANDTIME>60;
-- 复制输出的KILL语句执行
-- 或使用存储过程批量KILL(谨慎使用)
DELIMITER $$
CREATEPROCEDURE kill_long_queries()
BEGIN
DECLARE done INTDEFAULTFALSE;
DECLARE thread_id INT;
DECLARE cur CURSORFOR
SELECT id FROM information_schema.PROCESSLIST
WHERE COMMAND !='Sleep'ANDTIME>120;
DECLARE CONTINUE HANDLER FORNOT FOUND SET done =TRUE;
OPEN cur;
read_loop: LOOP
FETCH cur INTO thread_id;
IF done THEN
LEAVE read_loop;
END IF;
KILL thread_id;
END LOOP;
CLOSE cur;
END$$
DELIMITER ;
CALL kill_long_queries();
2. 临时调整参数
-- 临时增加最大连接数(治标不治本)
SETGLOBAL max_connections =2000;
-- 降低锁等待超时时间
SETGLOBAL innodb_lock_wait_timeout =10;
-- 增加连接超时时间,避免频繁重连
SETGLOBAL wait_timeout =600;
3. 重启MySQL(最后手段)
# 平滑重启
systemctl restart mysql
# 如果无法停止,强制终止
killall -9 mysqld
systemctl start mysql
⚠️ 重启风险:
-- 检查数据库状态
SHOWGLOBAL STATUS;
SHOW VARIABLES;
-- 检查主从复制状态(如果有)
SHOW SLAVE STATUS\G
-- 验证业务关键表
SELECTCOUNT(*) FROM critical_business_table;
-- 检查错误日志
tail -f /var/log/mysql/error.log
恢复业务后,必须进行详细复盘:
某电商平台在促销活动期间,MySQL数据库突然出现大面积卡死,订单系统、用户中心等核心服务全部不可用,影响用户数超过10万。
14:23 - 监控告警:MySQL连接数异常,API响应时间超过10s
14:24 - 运维团队接到告警,开始排查
14:25 - 执行第一步:确认MySQL进程正常,CPU 15%,load 80
14:26 - 执行第二步:发现1800+个连接,大量Waiting for table metadata lock
14:27 - 执行第三步:定位到一个执行了12分钟的ALTER TABLE操作
14:28 - 找到阻塞源头:一个未提交的SELECT查询(开启了事务)
14:29 - KILL阻塞查询和DDL操作
14:30 - 连接数开始下降,业务逐步恢复
14:35 - 所有服务恢复正常
autocommit=1为默认值innodb_lock_wait_timeout=10max_execution_time限制查询时间# my.cnf 关键配置
[mysqld]
# 连接管理
max_connections = 2000
max_connect_errors = 10000
wait_timeout = 600
interactive_timeout = 600
# 锁相关
innodb_lock_wait_timeout = 10
lock_wait_timeout = 5
table_open_cache = 4000
# 慢查询监控
slow_query_log = 1
long_query_time = 1
log_queries_not_using_indexes = 1
# 性能优化
innodb_buffer_pool_size = 物理内存的70%
innodb_log_file_size = 1G
innodb_flush_log_at_trx_commit = 2# 非金融业务可设为2
关键指标:
每日:
每周:
每月:
MySQL"卡死"是运维工作中最常见也最紧急的故障之一。通过本文介绍的5步排查法——确认进程状态、检查连接线程、定位锁等待、分析系统瓶颈、应急恢复处理——可以在大多数情况下快速定位问题并恢复业务。
随着云原生和分布式数据库的发展,传统MySQL的一些痛点正在被新技术解决:
但无论技术如何演进,深入理解数据库工作原理、掌握系统化的排查方法,始终是每个运维工程师的核心能力。希望这篇文章能帮助你在遇到MySQL"卡死"时,不再慌乱,而是有条不紊地快速解决问题。
记住:故障不可怕,可怕的是没有应对方法。
快捷导航网站 https://www.kjdaohang.com,近期发布大量AI实用工具、免费学习教程,欢迎PC端用户体验使用!
👇 点击下方关注公众号《测试开发技术》,获取免费测开学习路线、简历模板、面试真题、AI测试、AI 编程、自动化测试、测试开发资料教程等。