1. 项目概述
1.1 慢查询日志是什么
做Java后端开发,跟MySQL打交道是每天的必修课。大部分时候,我们写的SQL跑得挺快,但只要数据量一上来,或者SQL写得稍微糙一点,接口的响应时间就开始往上飙。这时候你要是两眼一抹黑,不知道从哪里下手排查,那日子就难过了。
慢查询日志(Slow Query Log)就是MySQL留给我们的一本“黑账本”,它会把所有执行时间超过你设定阈值的SQL语句原原本本记下来,包括这条SQL是什么时候执行的、花了多久、扫描了多少行、返回了多少行。有了这本账本,你就能精准定位到到底是哪条SQL在拖后腿,而不是靠猜、靠运气去优化。
这个功能对Java后端开发者来说尤其重要。因为Java项目大多是长时间运行的服务端应用,SQL性能问题往往不会立刻暴露,而是随着数据量增长慢慢浮现,可能在某个深夜批量任务集中运行时就突然把数据库打挂了。你总不可能24小时盯着数据库手动看,但慢查询日志可以替你盯着,把问题都记下来等你来翻。
文中我会把慢查询日志整个链路讲清楚,包括怎么开启、参数怎么调、日志怎么看、用什么工具分析、线上实战怎么运用,最后还会整理一些面试里常问的点。无论你是刚接触MySQL的初级开发,还是已经在排查线上问题的老手,这篇文章都可以当作一份实操手册来用。
1.2 为什么需要单独关注慢查询
现在Java面试八股文里,MySQL优化是个绕不开的大类。一问到SQL优化,大家都会背“避免SELECT *”“用索引”“小表驱动大表”,可你要是问一句“你怎么知道哪条SQL需要优化”,很多人就卡住了。
这里就是慢查询日志发挥作用的地方了。它不是优化手段,而是定位手段。没有它,你的优化就是无头苍蝇;有了它,你才能知道问题SQL具体长什么样,然后针对性建索引、改SQL、调参。
另一个实际场景是:你负责的Java服务突然在高峰期出现大量超时告警,DBA说数据库CPU跑满了。你要是没有任何历史日志依据,只能一脸懵。但如果提前开启了慢查询日志,你就能立刻翻出那段时间执行时间最长的SQL,通常就是罪魁祸首。
从我的实际经验看,慢查询日志排查的问题80%以上最后都归结为三类:缺索引、SQL写法导致索引失效、数据量太大且没有合理分页。这三类问题,在慢查询日志里都有非常明显的特征。学会看日志,就等于学会了给MySQL做体检。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 慢查询日志的运行机制与核心参数
2.1 一条慢SQL是如何被记录下来的
我们先把底层逻辑说清楚。MySQL在执行每一条SQL语句时,都会记录它的实际执行时间。当一条SQL执行完毕后,MySQL会拿这个时间和我们设定的阈值(也就是long_query_time)做比较,如果超过了阈值,并且慢查询日志功能本身是开启状态,那么这条SQL就会被写入慢查询日志文件。
整个过程不需要应用端做任何配合,对Java服务来说是透明的。你不改代码、不改连接池、不动ORM框架的配置,只要数据库层面开好这个开关,它就自动开始工作。这也意味着你可以随时在出问题之前就把开关打开,日常积累数据,等需要排查的时候直接查历史记录。
值得注意的一点是,MySQL记录的是“执行时间”,这个执行时间包含SQL在服务端实际执行的耗时,但不包含网络传输时间,也不包含客户端(比如Java应用)处理结果集的时间。也就是说,如果一条SQL在数据库执行只需要50毫秒,但传输到Java应用再解析成对象花了500毫秒,这部分时间慢查询日志是记录不到的。这一点在排查时要留意,不然会出现日志里找不到慢SQL,但接口就是慢的情况。
一条SQL被判定为慢SQL后,被记录的内容包含几个关键部分:
- SQL执行的时间点(什么时候执行的)
- 执行耗时(Query_time)
- 锁等待时间(Lock_time)
- 返回的行数(Rows_sent)
- 扫描的行数(Rows_examined)
- 完整的SQL语句文本
这几项信息配合起来使用,你能还原出这条SQL在数据库内部到底经历了什么。
2.2 关键参数逐个拆解
慢查询日志相关的参数主要集中在几个系统变量上,下面我把它们的功能、默认值和建议配置都列出来。
| 参数名 | 作用 | 默认值 | 建议值 |
|---|---|---|---|
| slow_query_log | 慢查询日志总开关 | OFF | ON |
| slow_query_log_file | 慢查询日志文件路径 | 主机名-slow.log | 按业务命名,如xxx-slow.log |
| long_query_time | 慢SQL阈值(秒) | 10 | 1~3,视业务而定 |
| log_queries_not_using_indexes | 是否记录没走索引的SQL | OFF | ON |
| log_throttle_queries_not_using_indexes | 每分钟最多记录多少条未走索引的SQL | 0(不限) | 10~20 |
| min_examined_row_limit | 扫描行数少于该值的SQL不记录 | 0 | 视业务而定 |
重点讲一下这几个参数之间是怎么配合的。
slow_query_log是总开关,这个不打开,后面所有参数都白搭。MySQL默认是关闭的,因为记录日志本身有I/O开销,MySQL官方为了默认性能考虑选择不开。但在实际生产中,尤其是对性能敏感的业务,这个开关建议从一开始就打开,因为它带来的开销和它解决的问题相比,完全值得。
long_query_time是阈值,默认10秒。10秒什么概念?一个Java接口如果数据库查询耗时超过10秒,用户体验已经是灾难级别了。所以默认值只适合用来兜底,不适合做日常监控。一般我建议设置成1~3秒。如果是高并发核心业务,可以设置成1秒甚至0.5秒;如果是内部管理系统、低并发应用,设置成3秒也够用。需要理解的是,这个值设得越小,记录下来的SQL越多,日志增长越快,需要分析的成本也越高。
long_query_time还有一个容易被忽略的细节:MySQL对于慢查询时间的判定是“大于”,而不是“大于等于”。也就是说,如果你设置long_query_time=1,那么一条SQL执行时间精确等于1秒时它是不会被记录的,必须大于1秒才会被记录。
log_queries_not_using_indexes这个参数我要特别说一下。它表示只要SQL没走索引,不管执行时间多短,都会被记录到慢查询日志里。这个参数在开发环境非常有用,因为很多SQL在数据量小的时候跑得飞快,但没走索引,等上线跑了一段时间数据量上来就原形毕露了。提前记录没走索引的SQL,就可以在数据量小的时候就发现潜在问题。
但这个参数在生产环境要小心使用。因为一旦开启,很多全表扫描的小查询都会疯狂写日志,可能导致日志文件快速增长,反而拖累数据库性能。解决办法就是配合log_throttle_queries_not_using_indexes使用,限制每分钟记录的数量。我一般建议生产环境每分钟限制10到20条就够了,既能捕捉到问题,又不至于刷爆磁盘。
min_examined_row_limit是一个反向过滤条件,表示扫描行数少于设定值的SQL一律不记录。这个参数可以用来过滤掉那些本身就很轻量的查询。比如你设置了min_examined_row_limit=100,那么一条SQL就算执行时间超过了long_query_time,只要它扫描的行数不到100行,也不会被记录。在日志量过大的时候,这个参数可以帮你减少无效记录。
2.3 实操:四步开启慢查询日志
接下来我们实际操作一遍。假设你手头有台MySQL服务器,版本是5.7或8.0,我们要把慢查询日志开起来。
第一步,查看当前的慢查询配置情况。
sql复制SHOW VARIABLES LIKE 'slow_query_log%';
SHOW VARIABLES LIKE 'long_query_time';
SHOW VARIABLES LIKE 'log_queries_not_using_indexes';
执行完你会发现,大部分参数可能都是OFF或者默认值。不要急,我们一步步改。
第二步,动态修改参数。MySQL支持在线修改这些参数,不需要重启服务,对生产环境非常友好。
sql复制SET GLOBAL slow_query_log = 'ON';
SET GLOBAL slow_query_log_file = '/data/mysql/logs/xxx-slow.log';
SET GLOBAL long_query_time = 1;
SET GLOBAL log_queries_not_using_indexes = 'ON';
SET GLOBAL log_throttle_queries_not_using_indexes = 10;
这里有个非常经典的坑:long_query_time修改后,对已经存在的连接不生效,只有新建立的连接才会使用新值。如果你用同一个终端连接执行SHOW VARIABLES LIKE 'long_query_time',会发现值没变。但如果你重新连接一次,就能看到新值了。这跟MySQL会话变量和工作机制有关,不是修改失败。
第三步,确认修改生效。重新连接MySQL后执行:
sql复制SHOW VARIABLES LIKE 'long_query_time';
这时候你应该能看到新值。还需要确认日志文件是否成功创建,可以用系统命令查看:
bash复制ls -lh /data/mysql/logs/xxx-slow.log
如果文件存在,说明慢查询日志已经在正常写入。
第四步,验证效果。我们人为执行一条慢SQL,比如:
sql复制SELECT SLEEP(2);
这条SQL会故意休眠2秒,超过我们设定的1秒阈值。执行完后,查看慢查询日志文件,应该能看到这条记录。验证完成后就可以放心使用了。
有一点需要提醒:用SET GLOBAL方式修改的参数,在MySQL服务重启后会恢复成配置文件里的值。如果你希望永久生效,需要在my.cnf或my.ini配置文件里加上对应配置项,比如:
ini复制[mysqld]
slow_query_log = 1
slow_query_log_file = /data/mysql/logs/xxx-slow.log
long_query_time = 1
log_queries_not_using_indexes = 1
log_throttle_queries_not_using_indexes = 10
改完配置文件后,重启MySQL服务才能生效。新部署的环境建议直接在配置文件里写好,省得每次手工开。
3. 慢查询日志的分析方法与工具链
3.1 日志文件里到底长什么样
打开慢查询日志文件,你会发现它其实是有固定格式的纯文本,可以直接用文本编辑器查看。下面是一条典型的慢查询记录:
log复制# Time: 2025-01-15T10:23:45.123456Z
# User@Host: order_service[order_service] @ [192.168.1.110] Id: 882314
# Query_time: 2.531250 Lock_time: 0.000173 Rows_sent: 10 Rows_examined: 523890
SET timestamp=1736922225;
SELECT * FROM order_detail WHERE user_id = 12345 AND status = 1 ORDER BY create_time DESC LIMIT 10;
逐行解读一下:
Time字段是SQL执行的时间点,注意这是UTC时间,如果你服务器在东八区,要加上8小时才是北京时间。
User@Host表示哪个数据库账号从哪个IP发起的连接,这个信息用来定位是哪个应用、哪台机器发的SQL非常有用。Java服务通常会用专门的账号连数据库,所以看到这个字段你就知道是哪个服务出的问题。
Query_time是这条SQL总的执行时间,2.531250秒。Lock_time是锁等待时间,0.000173秒,很小,说明这条SQL没有长时间等待锁。Rows_sent是最终返回给客户端的行数,10行。Rows_examined是这条SQL在存储引擎层面扫描了多少行,523890行,这是个非常关键的数字。
最后两行是SQL的详细信息,包括执行时间戳和完整的SQL文本。有了完整SQL文本,你就能拿到开发环境或者测试环境去执行EXPLAIN分析执行计划。
我见到很多刚接触慢查询日志的同学,拿到日志只会看Query_time,看到执行时间长的SQL就直接发到群里问怎么优化。但真正有经验的DBA和开发,最关注的是Rows_examined和Rows_sent的比例。如果Rows_examined是52万,Rows_sent只有10,说明这条SQL扫描了大量数据但最终只返回少量数据,典型的索引使用不当。正常使用索引的SQL,扫描行数应该和返回行数处于同一个量级。
3.2 手工分析:从日志到优化方案
我们还是以上面的SQL为例,讲讲怎么从一条慢SQL推导出优化方案。
SQL原始内容:
sql复制SELECT * FROM order_detail WHERE user_id = 12345 AND status = 1 ORDER BY create_time DESC LIMIT 10;
实际扫描了52万行,返回10行。这时候第一反应就应该是看执行计划,先确认索引情况。在MySQL命令行执行:
sql复制EXPLAIN SELECT * FROM order_detail WHERE user_id = 12345 AND status = 1 ORDER BY create_time DESC LIMIT 10;
假设执行计划显示type为ALL,也就是全表扫描,key为NULL,那就说明这张表连user_id上的索引都没有建。对一张有百万行数据的订单明细表做全表扫描,不慢才怪。
那么优化方案就很直接了,创建联合索引:
sql复制ALTER TABLE order_detail ADD INDEX idx_user_status_create (user_id, status, create_time);
这个索引的创建包含了三个字段,顺序也有讲究:user_id用于等值过滤,status用于等值过滤,create_time用于排序。这样索引既能快速定位到user_id=12345 AND status=1的记录,又能直接从索引里按create_time排好序读取,避免了额外的排序操作和回表。
创建索引后,再次执行同一条SQL,可能执行时间就从2.5秒降到了10毫秒以内。在没有慢查询日志之前,你从52万行数据里找到这样一条SQL,无异于大海捞针。但有了日志,问题SQL直接摆在面前,分析和优化就一气呵成。
3.3 工具链:mysqldumpslow与pt-query-digest
当慢查询日志积累了一段时间,可能有几千上万条记录,你不可能用眼睛一条条看。这时候就得靠工具来做统计分析。
MySQL自带的mysqldumpslow工具是最基础的分析工具。用法很简单:
bash复制mysqldumpslow -t 10 /data/mysql/logs/xxx-slow.log
这个命令会把日志中执行时间最长的前10条SQL汇总出来。它还支持按不同维度排序,比如:
bash复制mysqldumpslow -s c -t 10 /data/mysql/logs/xxx-slow.log # 按计数排序
mysqldumpslow -s l -t 10 /data/mysql/logs/xxx-slow.log # 按锁定时间排序
mysqldumpslow -s r -t 10 /data/mysql/logs/xxx-slow.log # 按返回行数排序
mysqldumpslow还有一个非常有用的特性:它会自动把SQL中的具体数值替换成N,把字符串替换成S。这样一条SELECT * FROM user WHERE id = 123和SELECT * FROM user WHERE id = 456会被归并为同一条SQL来统计。这样你看到的就是一个聚合结果,比如某个模板SQL出现了多少次、平均耗时多少,而不是被成千上万条相似SQL淹没。
另一个更强大的工具是Percona Toolkit里的pt-query-digest。这个工具能做的事情更多,它可以分析慢查询日志、通用日志、binlog,还能分析tcpdump抓取的网络包。在慢查询日志分析这个场景下,它输出的报告比mysqldumpslow详细得多,会按照SQL的响应时间占比做排名,给出每条SQL出现的次数、平均耗时、最大耗时、总耗时占比等全面的统计指标。
bash复制pt-query-digest /data/mysql/logs/xxx-slow.log > slow_report.txt
生成的报告会有一个“Profile”表,清晰展示了哪类SQL消耗了最多的数据库时间。在调优的时候,我一般先看占比最高的那几类SQL,因为解决它们对整体性能提升最明显。如果一条SQL一天出现一万次,每次花50毫秒,那么优化到20毫秒带来的整体收益,远大于优化一条每天只出现几次但耗时2秒的SQL。
pt-query-digest还支持把分析结果输出到数据库表中,方便你用SQL再查询统计。不过这个工具需要单独安装Percona Toolkit,属于第三方工具,如果没有安装条件,直接用mysqldumpslow也够用。
4. 实战案例:一次Java接口超时排查全记录
4.1 案例背景与初步定位
有一次我们团队负责的订单服务出了个线上问题,用户反馈“我的订单列表页面打开非常慢,有时候直接超时”。这个接口背后的逻辑是从订单表查询当前用户的订单列表,还要关联订单项表查明细。功能上线初期一切正常,但运行了几个月后,订单量增长到几百万级别,接口响应时间从之前的200毫秒慢慢恶化到10秒以上。
我接手排查时,首先看慢查询日志,因为这是最快定位数据库问题的途径。我用mysqldumpslow把最近几个小时的日志做了一个Top 10排序:
bash复制mysqldumpslow -t 10 /data/mysql/logs/order-slow.log | head -50
结果一目了然,排名第一的SQL就是这个订单列表查询,执行时间平均6.8秒,累计出现了几千次,占到了整个日志记录时间的70%以上。这就非常典型了——一个高频慢SQL,直接拖垮了接口,还连带着把数据库CPU打满,影响了同库其他业务。
4.2 慢SQL根因分析
把慢查询日志里的原始SQL捞出来,内容大致是这样的:
sql复制SELECT
o.id, o.order_no, o.total_amount, o.status, o.create_time,
i.sku_id, i.sku_name, i.quantity, i.price
FROM
order_info o
INNER JOIN order_item i ON o.id = i.order_id
WHERE
o.user_id = 10086
ORDER BY
o.create_time DESC
LIMIT 20;
从SQL本身来看,写法是规范的:用到了INNER JOIN、WHERE条件明确、有ORDER BY和LIMIT,看起来不像新手写的。但为什么这么慢?
执行EXPLAIN后发现,主表order_info走了user_id的索引,只过滤出该用户的少量订单;但订单表关联查询时,order_item表走了全表扫描。虽然单用户订单量不大,但order_item表整体有几百万行,基于全表扫描的关联匹配,每执行一次就要扫描数百行到数万行不等。
根因在于order_item表上没有针对order_id的索引。正常来说,order_item.order_id应该是一个外键性质的字段,建索引属于基本规范。但看建表语句时发现,当初设计表结构的人漏加了这个索引,以为有主键就够了。数据量小的时候,就算没索引也能跑得动,但数据量一上来就暴露了。
4.3 解决方案与效果
找到了根因,方案就很明确了。在order_item表的order_id上创建索引:
sql复制ALTER TABLE order_item ADD INDEX idx_order_id (order_id);
建索引这个操作在百万级表上执行,大概需要几秒到几十秒不等,期间会有锁表,所以我选择在业务低峰期执行。索引创建完成后,重新执行同一条SQL,耗时从6.8秒降到了80毫秒左右。
这个案例可以清晰地看到慢查询日志的价值。在整个排查过程中,我没有去翻业务代码、没有在测试环境复现,第一步就精准锁定了问题SQL,直接顺着日志找到了根因。如果没有慢查询日志,碰到这种接口超时问题,从应用层开始一层层排查,可能搞几个小时都找不到头绪。
4.4 案例复盘与延伸思考
事后我们做了个复盘,沉淀了几条经验。
第一,索引设计不是上线前做完就结束了,而是一个持续演进的过程。数据量增长、业务逻辑变化,都可能导致原有的索引不再适用。建议定期(比如每两周)分析一次慢查询日志,及时发现有问题的SQL。
第二,Oracle等数据库有专门的SQL调优顾问,但MySQL没有这么智能的东西,慢查询日志配合EXPLAIN就是最核心的手段。团队的新人培养,我也建议从看慢查询日志入手,而不是一上来就让他们背优化理论。看多了真实问题,自然就能理解那些优化理论的来源和意义。
第三,慢查询日志只是发现问题的第一步,怎么优化还取决于对业务的理解。比如上面案例中,如果只建order_id索引就能解决问题,那就没必要动SQL结构。但如果遇到更复杂的慢SQL,可能需要改写SQL、拆分查询、加缓存、分库分表等方案,这时候就需要结合业务场景综合判断了。
5. 慢查询日志使用中的坑与避坑指南
5.1 阈值设置不当导致日志爆炸
我刚工作那会儿,接手过一个历史项目,发现它的慢查询日志文件竟然有几十个GB,把磁盘都差点塞满了。一查配置,发现log_queries_not_using_indexes被设成了ON,但long_query_time设的是0。这意味着什么?所有SQL都会被记录,因为任何SQL的执行时间都大于0。加上没走索引的SQL也全部记录,日志文件爆炸性增长。
这个案例告诉我们两点。第一,long_query_time不要设置成0或者太小的值,除非你很清楚自己在做什么。第二,开启了log_queries_not_using_indexes后,一定要配合log_throttle_queries_not_using_indexes来限制记录数量,否则可能会出现日志风暴。
那怎么监控日志文件大小和增长速度?最简单的方法是写个定时任务或者用日志轮转工具,查看日志文件大小,超过一定阈值就做切割归档。MySQL本身不会自动切割慢查询日志,需要借助logrotate这类系统工具,或者手动定期重命名日志文件并执行FLUSH LOGS让MySQL重新创建日志文件。
5.2 日志记录对性能的隐性影响
慢查询日志本身对数据库性能是有影响的。这个影响主要是磁盘I/O层面的,因为每记录一条慢SQL,就是一次磁盘写入操作。在慢SQL特别多的情况下,写日志本身就会成为数据库的新负担。
不过对于绝大多数应用场景,这个影响是可以接受的。磁盘写入是顺序写的,MySQL内部有缓冲机制,实际开销并不大。但如果你的业务本身SQL质量就很差,慢日志每秒要记录几百条,那就另当别论了。处理方式就是前面说的,合理设置阈值和分析工具的配合,先大刀阔斧解决掉最严重的一批问题SQL,再慢慢收窄监控范围。
另外提醒一点:不要在业务高峰期临时去开启慢查询日志做诊断。如果日志量巨大,瞬间的磁盘I/O压力可能让本就不稳定的数据库雪上加霜。建议低峰期开启,或者在生产环境提前长期开启(配合合适的阈值),而不是临时抱佛脚。
5.3 与Java应用相关的几个补充关注点
Java应用使用数据库连接池(如HikariCP、Druid)时,慢查询日志里记录到的SQL来源IP会是应用服务器的IP。如果应用部署在多台机器上,可以通过IP区分是哪个节点产生的慢查询。但要注意,如果应用和数据库之间有Proxy(如MyCat、ShardingSphere),来源IP会是Proxy的IP,这时候定位会麻烦一些。
还有一种常见场景:Java服务里面用了批量操作,比如saveBatch这种,底层通常是一条SQL插入几百条数据。这种SQL在慢查询日志里会显示为一条很长的批量SQL,耗时可能较长。但这类慢SQL不一定需要优化,因为批量操作总体效率可能比逐条插入高出很多。所以看到慢日志,要对SQL有一个分类判断,不是所有慢SQL都必须优化成毫秒级,有些场景下的批量操作,只要整体时间可控,是可以接受的。
另外一个容易被忽略的点是:Java应用如果使用了MyBatis-Plus这类框架,动态生成的SQL可能比较复杂,慢查询日志里记录的SQL难以直观看出对应的是哪个Mapper方法。建议在开发时给SQL加上注释(如/* mapper: UserMapper.selectPage */),这样慢查询日志里就能看到自定义注释,方便快速定位到代码位置。
5.4 常见问题速查表
| 现象 | 可能原因 | 解决思路 |
|---|---|---|
| 日志文件增长过快 | long_query_time太小或log_queries_not_using_indexes开启且未限流 | 调大阈值,限制每分钟记录条数,定期切割日志 |
| 修改long_query_time后不生效 | 当前连接仍然使用旧会话变量 | 重新连接MySQL,或在配置文件中持久化设置 |
| 日志里找不到预期的SQL | SQL执行时间未超过阈值,或被min_examined_row_limit过滤 | 临时调低阈值验证,检查过滤条件 |
| 日志时间与本地时间不一致 | MySQL记录的是UTC时间 | 手动换算时区,或设置log_timestamps参数 |
| 磁盘被慢日志占满 | 长期未清理日志文件 | 配置logrotate自动轮转,定期归档删除 |
| 同一个SQL大量重复记录 | 高频低效SQL,应用层面频繁调用 | 优先优化该SQL,或应用层加缓存降低频率 |
6. 面试视角:慢查询日志相关的经典问题
6.1 MySQL调优的整体思路是什么
很多Java面试者在被问到MySQL调优时,一上来就开始背“索引优化、SQL改写”,但完整的调优思路其实是有先后顺序的。比较标准的回答框架是:先定位问题,再分析原因,最后实施优化。
定位问题的手段就是今天讲的慢查询日志。没有这个前置步骤,后续的所谓优化都缺乏针对性,大概率是盲人摸象。一个完整的回答可以是这样的:
MySQL调优我一般分几步来做。第一,开启慢查询日志,把执行时间超过阈值的SQL捞出来,这一步解决的是“找出问题SQL”。第二,针对每一条慢SQL,用EXPLAIN查看执行计划,重点看type、key、rows这几个字段,判断SQL是否走了合适的索引、扫描行数是否过大,这一步解决的是“分析为什么慢”。第三,根据分析结果实施优化,包括加索引、改写SQL、调整表结构、分库分表等手段。第四,优化后回到慢查询日志验证效果,确认问题SQL的执行时间降到了合理范围。最后,对于高频访问的查询,还可以考虑在Java应用层加缓存,降低数据库压力。
6.2 一条慢SQL你能想到哪些优化方向
面试官给你一条慢SQL,问你怎么优化,考察的就是你的知识广度。可以从这几个层面来组织答案。
SQL写法层面,看看是不是有隐式类型转换、函数包裹索引列、前导模糊查询、OR条件连接不当等问题导致索引失效。比如WHERE user_id + 1 = 100这种写法就会导致索引失效,应该改成WHERE user_id = 99。
索引设计层面,考虑是否缺少合适的索引,或者已有索引字段顺序是否合理。这里要注意区分单列索引和联合索引的差别,联合索引要遵循最左前缀原则。还是那句话:索引不是越多越好。MySQL优化器每个查询只能选择一个索引使用,索引过多不仅占用空间,还会拖慢INSERT、UPDATE、DELETE操作的速度,因为每次写入要同步维护多个索引树。
SQL结构层面,看看能不能通过改写减少扫描的数据量。比如把一个大查询拆成多个小查询,把子查询改成JOIN,合理利用EXISTS和IN的场景差异,都可以在一定程度上提升查询效率。
最后一层是架构层面,如果单表数据量实在太大,索引怎么建都不理想,就要考虑分库分表、读写分离、引入缓存中间件等手段。这一层的变化会影响Java应用代码结构,属于比较大的改动。回答框架里能把这些层面讲清楚,说明你对问题理解的纵深感是够的。
6.3 一份自我检测清单
下面这些问题,你可以拿来测一测自己到底掌握了多少。
- 你们项目的慢查询日志开启了没有?阈值设置的多大?
- 如何确认慢查询日志真的在正常工作?
- 慢查询日志里哪些字段是最值得关注的?为什么?
- 一条SQL扫描50万行返回10行,你会怎么优化?
mysqldumpslow和pt-query-digest各有什么优势?- 慢查询日志文件越来越大怎么办?
long_query_time改成0会有什么后果?
如果这些问题你能不看资料脱口而出,那么不管是日常工作排查还是在面试中聊MySQL调优,你都能做到心里有底,而不是单纯背下来几条需要拼运气的理论。
7. 从入门到实践:习惯成自然
MySQL慢查询日志本身功能不复杂,核心就是开关几个参数、看懂一个文件、会用两个分析工具。但真正难的是把它变成一种工作习惯,让日志分析成为日常开发流程中的固定一环。
我的建议从一开始就把慢查询日志开起来。日志文件就算只有几百MB,只要定期归档清理,对磁盘影响有限。但它可能在某一天帮你快速定位一个棘手的线上问题,这种价值很难用磁盘那点成本来衡量。对Java开发来讲,能写出功能正确的SQL只是及格线,能写出高性能的SQL并会排查SQL问题才算真正的进阶。
建议大家拿到一个不算熟悉的数据模型时,第一件事就是跑几个典型查询看看执行计划,如果发现全表扫描马上记录一下,分析是不是有隐患。被动等慢查询日志把问题暴露出来,是做故障响应,主动发现潜在问题,是做性能预防,两者之间隔着的正是对数据结构的熟悉程度和优化的敏感度。坚持那么一两个月,你再看SQL的眼光会和之前完全不同。
