面试官最爱问的MySQL优化八连击(真实踩坑版)
一、索引优化的玄学现场
说到索引优化,大家可能都背过"最左前缀原则",但实战中的坑可比这刺激多了!上周刚有个哥们儿在线上搞了个大事故——给status字段加了个单列索引,结果查询速度反而从0.1s暴增到2s!(惊不惊喜?意不意外?)
重点来了:status这种低区分度的字段单独建索引,不如和create_time搞组合索引。看这个死亡案例:
```sql
-- 错误示范 ❌
ALTER TABLE orders ADD INDEX idx_status(status);
-- 正确姿势 ✅
ALTER TABLE orders ADD INDEX idx_status_time(status, create_time);
```
用EXPLAIN一看就露馅了:type=ref直接变range,扫描行数从百万级降到几十行!不过要注意组合索引的字段顺序,把区分度高的放前面才是王道。
二、Join操作的惊天大雷
最近面试发现80%的候选人都会说"小表驱动大表",但真正理解的有几个?咱们用血淋淋的案例说话:
某电商系统有个user表(1000w行)和order表(1亿行),要查VIP用户的最近订单。新手写的SQL直接让数据库原地爆炸:
sql
-- 死亡写法 💀
SELECT * FROM order
LEFT JOIN user ON order.user_id = user.id
WHERE user.vip_level > 3
ORDER BY order.create_time DESC
LIMIT 10;
知道问题在哪吗?这写法先做全表JOIN再过滤,相当于把两个表的笛卡尔积都算了一遍!优化方案其实巨简单:
sql
-- 救世主写法 😇
SELECT * FROM (
SELECT id FROM user
WHERE vip_level > 3
LIMIT 1000
) AS vip_users
INNER JOIN order ON vip_users.id = order.user_id
ORDER BY order.create_time DESC
LIMIT 10;
这个子查询先把VIP用户限制在1000人内,后续JOIN直接减少99.9%的数据量!(实测响应时间从15秒降到0.2秒)
三、深分页的死亡陷阱
LIMIT 100000,10这种写法坑了多少人?最近有个系统凌晨报警,CPU直接飚到99%!打开慢查询日志一看,全是要命的深分页。
教你们个黑科技——游标分页法:
```sql
-- 传统写法(100万数据后直接暴毙)
SELECT * FROM products
ORDER BY id
LIMIT 1000000, 10;
-- 续命写法(响应时间稳定在10ms内)
SELECT * FROM products
WHERE id > 1000000
ORDER BY id
LIMIT 10;
```
但要注意必须用自增主键且有序!如果业务必须用随机跳页怎么办?上覆盖索引啊:
sql
SELECT * FROM products
INNER JOIN (
SELECT id FROM products
ORDER BY create_time
LIMIT 1000000,10
) AS tmp USING(id);
先快速定位ID,再回表查数据,性能直接起飞!
四、统计查询的骚操作
统计类查询最容易翻车,特别是COUNT()和SUM()。有个DBA朋友被COUNT(*)坑惨了——2000万数据的表统计居然要8秒!
后来发现是用了MyISAM引擎,虽然COUNT(*)快但事务支持烂。换成InnoDB后,用这个技巧瞬间提速:
```sql
-- 常规写法(全表扫描)
SELECT COUNT(*) FROM user WHERE status=1;
-- 空间换时间
ALTER TABLE user ADD INDEX idx_status_cover(status, id);
```
然后在代码层做定时统计缓存。更狠的可以直接上Redis计数器,实时更新统计值。
五、连接池的隐藏BOSS
这绝对是面试官最爱挖的坑!很多候选人配置了连接池参数就以为万事大吉,结果半夜被报警叫醒——连接池爆了!
真实案例:某系统配置了maxTotal=200,结果高峰期出现大量ConnectionTimeoutException。最后发现是没配置合理的等待时间,加上慢查询堆积导致的雪崩。
推荐配置模板(基于Druid):
```properties
最大活跃连接数(按实际压测调整)
druid.maxActive=50
初始化连接数
druid.initialSize=5
最小空闲连接数
druid.minIdle=5
获取连接超时时间(单位毫秒)
druid.maxWait=500
定期检查空闲连接
druid.testWhileIdle=true
验证连接的SQL
druid.validationQuery=SELECT 1
```
重点:连接数不是越大越好!根据TP99响应时间计算:连接数 = TPS * 耗时(s)。比如TPS 100,平均耗时0.1s,理论需要10个连接就够了。
六、死锁的魔幻剧场
分享一个经典死锁场景,保证你面试时让面试官眼前一亮:
事务A:
sql
UPDATE account SET balance=balance-100 WHERE user_id=1;
UPDATE account SET balance=balance+100 WHERE user_id=2;
事务B:
sql
UPDATE account SET balance=balance+50 WHERE user_id=2;
UPDATE account SET balance=balance-50 WHERE user_id=1;
看似人畜无害的两个事务,在高并发下可能发生死锁!因为MySQL的锁是逐步获取的,两个事务可能互相持有对方需要的锁。
解决方案:统一更新顺序,比如始终先更新user_id小的账户。或者使用SELECT ... FOR UPDATE先锁定所有相关记录。
七、配置参数的天坑合集
my.cnf里这几个参数调不好,分分钟让你体验运维的"快乐":
- innodb_buffer_pool_size:建议设为物理内存的60-80%
- sync_binlog和innodb_flush_log_at_trx_commit:双1配置保证安全,但性能下降。允许数据丢失的话可以调成0/2
- max_connections:别设太大!配合连接池使用
- transaction_isolation:默认REPEATABLE-READ,但某些场景用READ-COMMITTED更好
最骚的操作:线上修改参数不用重启!
sql
SET GLOBAL innodb_buffer_pool_size=2147483648; -- 动态调整缓冲池
SET GLOBAL max_connections=500; -- 注意不能超过配置文件的限制
八、终极灵魂拷问
面试最后大概率会被问:"如果让你设计一个高并发的订单系统,MySQL要怎么优化?"
标准答案模板:
1. 分库分表(用户ID哈希)
2. 读写分离(主从架构+代理层)
3. 热点数据缓存(Redis抗瞬时高峰)
4. 异步削峰(MQ解耦)
5. 数据归档(冷热分离)
6. 限流降级(熔断机制)
但真正加分的是说细节:
- 分表后如何避免跨库查询
- 唯一ID的雪花算法实现
- 如何解决分布式事务
- 缓存和数据库的双写一致性方案
- 慢SQL的实时监控报警
最后的小贴士
记住这几个救命指令:
sql
SHOW ENGINE INNODB STATUS; -- 查看死锁信息
EXPLAIN ANALYZE SELECT ...; -- 新版MySQL的性能分析
ALTER TABLE ... ALGORITHM=INPLACE; -- 在线DDL不锁表
优化没有银弹,一定要结合业务场景!去年我们有个系统把CHAR(32)改成VARCHAR(32),存储空间直接省了40%,查询速度提升30%——所以字段类型选择这种基础问题也不能忽视啊!
浙公网安备 33010602011771号