1. MySQL性能优化实战指南
作为Java后端开发者,MySQL的性能优化是绕不开的核心技能。记得我刚入行时,第一次遇到生产环境慢查询导致接口超时,手忙脚乱地翻文档查日志的场景还历历在目。经过多年实战,我总结出一套系统的MySQL优化方法论,今天就从最实际的案例出发,带你掌握这些必知必会的硬核知识。
1.1 慢查询定位三板斧
当系统出现性能瓶颈时,快速定位慢查询是第一步。我常用的三种方式:
1.1.1 专业监控系统(推荐方案)
我们项目使用Skywalking搭建的全链路监控系统,在它的拓扑图中可以清晰看到:
- 接口平均响应时间
- 慢SQL执行耗时占比
- 数据库连接池状态
关键技巧:在压测时重点关注TP99指标,这比平均响应时间更能反映真实用户体验。我曾遇到一个案例,平均响应800ms看似正常,但TP99高达5秒,最终发现是某条SQL在特定参数下会出现全表扫描。
1.1.2 原生慢查询日志
如果没装监控系统,MySQL自带的慢查询日志是救命稻草。配置方法:
# my.cnf配置 slow_query_log = ON long_query_time = 2 # 超过2秒的查询 slow_query_log_file = /var/log/mysql/mysql-slow.log log_queries_not_using_indexes = ON # 记录未使用索引的查询分析日志时推荐使用mysqldumpslow工具:
# 统计最慢的10条SQL mysqldumpslow -s t -t 10 /var/log/mysql/mysql-slow.log1.1.3 实时诊断工具
紧急排查时可以用SHOW PROCESSLIST:
-- 查看当前执行中的SQL SHOW FULL PROCESSLIST; -- 配合KILL命令终止问题查询 KILL [query_id];1.2 EXPLAIN深度解析
拿到慢SQL后,EXPLAIN是我们的手术刀。这是我最常用的分析模板:
EXPLAIN FORMAT=JSON SELECT * FROM orders WHERE user_id=100 AND status='PAID';重点关注这些字段:
| 字段 | 解读要点 | 优化方向 |
|---|---|---|
| type | ALL全表扫描需警惕 | 添加合适索引 |
| key | 实际使用的索引 | 检查索引选择性 |
| rows | 预估扫描行数 | 超过1万需优化 |
| Extra | Using filesort/Using temporary最危险 | 优化排序字段索引 |
真实案例:某电商平台订单查询接口超时,EXPLAIN发现虽然使用了user_id索引,但rows显示扫描了8万行。原因是该用户是测试账号,产生了大量历史订单。最终通过添加(user_id, create_time)复合索引,并限制查询时间范围,将扫描行数降到200行以内。
1.3 索引优化实战
1.3.1 B+树索引原理
为什么MySQL选择B+树?通过这个对比实验就明白了:
-- 创建测试表 CREATE TABLE index_test ( id INT PRIMARY KEY, data VARCHAR(255), INDEX idx_data (data) ) ENGINE=InnoDB; -- 插入10万条数据 DELIMITER // CREATE PROCEDURE insert_test_data() BEGIN DECLARE i INT DEFAULT 0; WHILE i < 100000 DO INSERT INTO index_test VALUES(i, CONCAT('data-', FLOOR(RAND()*1000))); SET i = i + 1; END WHILE; END// DELIMITER ; CALL insert_test_data();执行范围查询:
-- 有索引的情况 SELECT * FROM index_test WHERE data BETWEEN 'data-100' AND 'data-200'; -- 强制不使用索引 SELECT * FROM index_test IGNORE INDEX(idx_data) WHERE data BETWEEN 'data-100' AND 'data-200';你会发现索引查询速度是非索引查询的50倍以上,这就是B+树叶子节点双向链表带来的优势。
1.3.2 聚簇索引陷阱
很多开发者不知道的是,InnoDB的聚簇索引会导致一些特殊现象:
-- 表结构 CREATE TABLE employees ( emp_no INT, dept_no CHAR(4), PRIMARY KEY (emp_no), INDEX idx_dept (dept_no) ); -- 查询1: 走索引但需要回表 EXPLAIN SELECT * FROM employees WHERE dept_no='D001'; -- 查询2: 覆盖索引优化 EXPLAIN SELECT emp_no FROM employees WHERE dept_no='D001';第一个查询虽然使用了idx_dept索引,但需要回表查完整数据。第二个查询只需要扫描索引树,效率提升3-5倍。
1.4 SQL语句优化技巧
1.4.1 分页优化方案
常见的分页性能问题:
-- 低效写法 SELECT * FROM large_table LIMIT 1000000, 10;优化方案对比:
| 方案 | 实现方式 | 适用场景 |
|---|---|---|
| 延迟关联 | 先查ID再关联 | 通用方案 |
| 书签记录 | 记录上次查询位置 | 连续分页 |
| 游标分页 | 使用WHERE条件过滤 | 无限滚动 |
具体实现:
-- 延迟关联 SELECT t.* FROM large_table t JOIN (SELECT id FROM large_table ORDER BY create_time LIMIT 1000000, 10) tmp ON t.id = tmp.id; -- 书签记录(假设上次最后一条create_time为'2023-06-01 12:00:00') SELECT * FROM large_table WHERE create_time > '2023-06-01 12:00:00' ORDER BY create_time LIMIT 10;1.4.2 JOIN优化原则
这是我总结的JOIN优化优先级:
- 优先使用INNER JOIN
- 确保关联字段有索引
- 小表驱动大表
- 避免复杂的ON条件
错误示范:
-- 大表驱动小表 SELECT * FROM large_table l JOIN small_table s ON l.id=s.l_id; -- 优化后 SELECT * FROM small_table s JOIN large_table l ON s.l_id=l.id;2. 事务与锁机制深度剖析
2.1 事务隔离级别实战
不同隔离级别的表现差异:
-- 会话1 START TRANSACTION; UPDATE accounts SET balance=balance-100 WHERE user_id=1; -- 会话2 SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT balance FROM accounts WHERE user_id=1; -- 可能读到未提交的修改 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT balance FROM accounts WHERE user_id=1; -- 只能读到提交后的数据MVCC实现关键点:
- 每行数据有隐藏的DB_TRX_ID字段记录事务ID
- undo log构成版本链
- ReadView判断可见性
2.2 死锁分析与预防
典型死锁场景重现:
-- 事务1 START TRANSACTION; UPDATE table_a SET name='a' WHERE id=1; UPDATE table_b SET name='b' WHERE id=1; -- 事务2 START TRANSACTION; UPDATE table_b SET name='b' WHERE id=1; UPDATE table_a SET name='a' WHERE id=1;预防措施:
- 统一资源访问顺序
- 降低事务粒度
- 设置锁超时:innodb_lock_wait_timeout=50
2.3 日志机制解析
redolog和binlog的二阶段提交流程:
- 准备阶段:写入redolog(prepare状态)
- 写入binlog
- 提交阶段:redolog标记为commit
崩溃恢复逻辑:
- binlog有记录:提交事务
- binlog无记录:回滚事务
3. 高可用架构设计
3.1 主从复制配置
搭建步骤:
# 主库配置 [mysqld] server-id=1 log-bin=mysql-bin binlog-format=ROW # 从库配置 [mysqld] server-id=2 relay-log=mysql-relay-bin read-only=1监控复制延迟:
SHOW SLAVE STATUS\G -- 关注Seconds_Behind_Master字段3.2 分库分表实践
我们项目的分片规则配置示例(使用ShardingSphere):
spring: shardingsphere: datasource: names: ds0,ds1 sharding: tables: t_order: actual-data-nodes: ds$->{0..1}.t_order_$->{0..15} table-strategy: inline: sharding-column: order_id algorithm-expression: t_order_$->{order_id % 16} database-strategy: inline: sharding-column: user_id algorithm-expression: ds$->{user_id % 2}遇到的一个坑:跨库JOIN查询性能极差,最终改造为:
- 冗余必要字段
- 使用广播表
- 业务层组装数据
4. 性能优化检查清单
最后分享我的MySQL优化检查表,每次上线前都会核对:
- [ ] 所有查询都使用EXPLAIN验证过执行计划
- [ ] 超过10万行的表都有合适索引
- [ ] 没有出现Using temporary/filesort
- [ ] 事务粒度控制在合理范围
- [ ] 批量操作使用rewriteBatchedStatements=true
- [ ] 连接池配置合理(建议HikariCP)
- [ ] 监控告警阈值设置妥当
记得有次大促前,我们通过这个清单发现了一个没有索引的VIP用户查询接口,及时优化避免了可能的生产事故。MySQL优化就是这样,需要在理论和实践中不断积累经验。