首页 / m.itcdj.com / 正文

数据库慢查询定位与优化一线实战手记

上周三凌晨两点十七分,工位上的笔记本突然狂震,告警群刷了三百多条消息——核心交易库的CPU直接干到97%,用户端支付接口的超时率飙到42%。
别慌。
真的。
刚工作那会碰到这种场面,我手都抖,上去就把processlist里跑了几十秒的查询挨个kill,结果把财务跑月度对账的批处理给打断了,第二天被追着骂了三层楼。
后来才学乖,碰到慢查询告警,第一步永远是留证,不是动手。

告警先留证,别上来就动生产


先把show full processlist的全量输出存成本地文件,把慢查询日志里最近1小时的条目导出来,甚至要把当时的服务器负载、磁盘IO、连接数这些指标截图存好。
MySQL慢查询日志实时监控告警界面

很多人上来就清缓存、kill进程、改配置,等现场破坏完了,根本不知道当时到底是哪条查询拖垮了数据库。我上次碰到个运维同事,碰到CPU高直接把数据库重启了,重启完啥日志都没留,过了三天同样的问题又犯,折腾了整整一个通宵才找到是个没加索引的报表查询在跑。
所有的排查动作,都要建立在完整的现场证据上,没有证据的操作本质上就是瞎碰运气。
别一看到慢查询就说数据库不行,很多时候是业务侧的问题,比如大促的时候本来流量就高,再加个没做好缓存的查询,直接就把连接占满了。

别迷信加索引就能解决所有问题


拿到慢查询的条目,第一反应很多人就是跑explain,看到type列是ALL,就喊着要加索引。加完跑一遍,好像是快了点,过两天数据涨了,又慢回去了。
MySQL执行计划explain结果截图
MySQL执行计划explain结果截图

我上个月碰到个很典型的例子:订单表上的查询,where条件是create_time > ‘2024-09-01’ and status = 1,开发同学给user_id字段加了个普通索引,执行计划里显示确实用到了这个索引,但是rows列估算的扫描行数是120万,实际跑下来要11秒。为啥?因为表的统计信息停在三个月前,优化器误以为走user_id索引的成本更低,根本没走建在create_time上的联合索引。
这种情况你加再多索引都没用,先跑一句analyze table order_info把统计信息更新了,再看执行计划,直接走对索引,查询耗时从11秒降到80ms。
看执行计划不能只看“有没有用到索引”,重点看rows估算值和实际扫描行数的偏差,偏差超过一个数量级的,先更新统计信息,别上来就改代码。
还有那种最离谱的模糊查询,select * from user where name like ‘%张’,这种查询你加什么索引都白搭,前导模糊查询天生没法走B树索引,要么改业务逻辑用全文索引,要么提前把匹配结果存到缓存里,别硬往数据库上怼。

优化完别直接关工单


很多人把慢查询从10秒优化到100ms,就觉得万事大吉,关工单下班。结果过了两周,同样的查询又变成5秒,甚至10秒。
为啥?
数据量涨了啊。
上次碰到个运营后台的列表查询,写的是limit 10000, 20,刚上线的时候表上才几万条数据,跑30ms快得很,等数据涨到100万条,这个查询要扫10020行,把前面10000行全部读出来扔掉,再返回后面20行,直接跑7秒。这种深分页的坑,真的是上线的时候好好的,数据一涨就炸。
所以现在我们组的规矩是,只要是上线的慢查询优化项,必须把对应的查询耗时、扫描行数加到监控面板里,设好阈值——超过500ms就自动发告警,根本不用等用户投诉了才发现问题。
说实话,这些事真的没什么技术门槛,就是每次踩坑的时候多记一笔,别下次栽同一个跟头。
不过话说回来,很多人总觉得数据库优化是DBA的责任,跟写业务代码的没关系。上次碰到个开发写的查询,把订单表和用户表、商品表、支付表全关联了,没有任何过滤条件,一次把三个大表全扫了,跑了40秒过来找我说数据库性能太差。我给他指了指那个没加where条件的关联,他哦了半天,说写的时候忘了加用户ID的过滤条件。
对吧?
哪有那么多玄学的优化技巧,大部分的慢查询,本质上就是写代码的时候没走心而已。