最近在维护一个日活百万级的电商平台数据库时,遇到了CPU使用率持续飙高的问题。高峰期CPU使用率经常达到90%以上,导致订单查询延迟、用户登录超时等严重问题。经过一周的排查和优化,最终将CPU使用率稳定控制在30%以下。下面分享我的实战经验和具体解决方案。
MySQL作为最流行的关系型数据库,CPU使用率过高是DBA和开发人员经常遇到的性能问题。当CPU使用率接近100%时,数据库响应速度会明显下降,表现为查询变慢、连接超时、甚至出现死锁和报错。这种情况在业务高峰期尤为常见,直接影响用户体验和系统稳定性。
慢SQL是导致CPU使用率高的头号杀手。在我处理的案例中,一个未优化的商品搜索SQL单次执行就需要3秒,高峰期每分钟执行上千次,直接吃掉了40%的CPU资源。
慢SQL的危害在于:
识别慢SQL的黄金标准是慢查询日志。通过以下配置开启:
sql复制slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 1 # 超过1秒的查询
log_queries_not_using_indexes = 1 # 记录未使用索引的查询
我们的系统在促销活动期间,QPS从平时的2000激增到15000,CPU使用率随之飙升。高并发导致:
监控关键指标:
sql复制show global status like 'Threads_connected'; # 当前连接数
show global status like 'Threads_running'; # 活跃线程数
show global status like 'Queries'; # 查询速率
错误的索引策略会造成双重伤害:
通过执行计划分析索引使用情况:
sql复制EXPLAIN SELECT * FROM orders WHERE user_id=100;
重点关注:
在一次排查中,发现一个报表查询扫描了2000万行数据。全表扫描的典型特征:
原始查询:
sql复制SELECT * FROM products
WHERE category='electronics'
AND price BETWEEN 1000 AND 2000
ORDER BY create_time DESC;
优化步骤:
sql复制ALTER TABLE products ADD INDEX idx_category_price_time(category, price, create_time);
sql复制SELECT id, name, price FROM products -- 只查必要字段
WHERE category='electronics'
AND price BETWEEN 1000 AND 2000
ORDER BY create_time DESC
LIMIT 100; -- 添加分页
优化效果:执行时间从2.3秒降至0.05秒。
配置拓扑:
code复制主库(写) -> 从库1(读)
-> 从库2(读)
-> 从库3(报表)
使用ProxySQL实现自动路由:
sql复制INSERT INTO mysql_servers(hostgroup_id,hostname,port) VALUES
(10,'master',3306),
(20,'slave1',3306),
(20,'slave2',3306);
推荐配置:
properties复制# HikariCP配置示例
maximumPoolSize=50
minimumIdle=10
connectionTimeout=30000
idleTimeout=600000
maxLifetime=1800000
检查未使用索引:
sql复制SELECT * FROM sys.schema_unused_indexes;
定期优化表:
sql复制ANALYZE TABLE orders;
OPTIMIZE TABLE orders; -- 对InnoDB效果有限
错误写法:
sql复制SELECT * FROM users WHERE DATE(create_time)='2023-01-01';
正确写法:
sql复制SELECT * FROM users
WHERE create_time BETWEEN '2023-01-01 00:00:00' AND '2023-01-01 23:59:59';
sql复制SELECT * FROM users FORCE INDEX(idx_username)
WHERE username LIKE 'john%';
使用performance_schema监控:
sql复制-- 查看消耗CPU最多的SQL
SELECT digest_text, sum_timer_wait/1000000000 AS latency_sec
FROM performance_schema.events_statements_summary_by_digest
ORDER BY sum_timer_wait DESC LIMIT 10;
sql复制SHOW PROCESSLIST;
关键状态解读:
Linux工具组合:
bash复制top -H -p $(pgrep mysqld) # 查看MySQL线程CPU使用
iotop -oPa # 磁盘IO监控
perf top -p $(pgrep mysqld) # 函数级性能分析
每周执行检查清单:
预测公式:
code复制所需CPU核心数 = (QPS × 平均查询时间(ms)) / (1000 × 目标利用率)
例如:
code复制(5000 × 50ms) / (1000 × 0.7) ≈ 36核心
关键参数调整:
ini复制innodb_buffer_pool_size = 12G # 总内存的50-70%
innodb_io_capacity = 2000 # SSD建议值
innodb_flush_neighbors = 0 # SSD建议禁用
table_open_cache = 4000
当CPU突然飙高时:
sql复制SELECT * FROM sys.session WHERE cpu_time > 1000 ORDER BY cpu_time DESC;
sql复制KILL [process_id];
sql复制SET GLOBAL max_connections = 100;
在最近一次大促备战中,我们通过以下组合拳将CPU使用率从95%降至35%:
特别提醒:索引不是越多越好。我们曾因过度索引导致写入性能下降30%,后来通过定期索引审查找到了平衡点。