前几天帮一个做电商的朋友排查线上问题,他们的订单查询接口平均耗时3.8秒,用户反馈已经炸了锅。我打开慢查询日志一看,好家伙,一个联表查询扫了全表,索引一个没走,每次请求还在循环里查了十几次数据库。改完索引和查询逻辑之后,接口耗时压到了200毫秒以内,整整快了19倍。你说这算不算翻倍?这都快翻出天际了。

很多人一说查询优化,脑子里蹦出来的第一反应就是加索引。这话没错,但加索引不是万能的,甚至加错了反而帮倒忙。比如有个经典误区:在低选择性的列上建索引,像性别字段,就男和女两个值。你建了索引,优化器一看选择性太低,照样走全表扫描,索引白建了不说,还占了额外空间,写操作还得维护这个没用的B+树。真正该建索引的,是那些where条件里高频出现、区分度又高的列,比如订单号、用户ID、手机号。而且联合索引有最左前缀原则,你建了(a,b,c)的联合索引,查询条件只带b和c,索引根本用不上。这就像你按拼音查字典,却只告诉人家这个字有俩撇,人家没法帮你翻页。
再说一个很多人容易忽略的点:SELECT 是性能杀手。你写select 的时候,数据库得把每行所有列的数据都读出来,传到应用层。如果表有二十个字段,你只需要其中三个,剩下十七个字段的数据全是在白忙活。网络传输的字节数多了,内存里存的对象大了,GC压力也上来了。我之前见过一个报表系统,改成只查需要的字段之后,同样的查询从800毫秒降到了300毫秒。别小看这个改动,它不费脑子,收益却实实在。尤其是那些大字段,比如text、blob类型的,你根本用不上,却每次都要被读一遍,纯属浪费。
分页查询也是个重灾区。很多人的分页写法是limit 100, 20,意思是跳过前面十万条,再取二十条。数据库得先把十万条数据都扫一遍,然后丢掉,再返回你要的二十条。这个成本会随着页码增大而指数级上升。更聪明的做法是用游标分页,或者叫键集分页。比如按id排序,你记录上一页最后一条的id,下一页直接写where id > 某个值 limit 20。这样不管翻到第几页,扫描的都只是那二十条。我有个客户,他们的管理后台翻到五十页之后,查询要五秒多,改成游标分页之后,稳定在五十毫秒以内。你感受下这个差距。
还有一类问题特别隐蔽,就是函数操作导致索引失效。比如你在where条件里写了where date(createtime) = '2024-01-01',或者where year(createtime) = 2024。你觉得自己建了createtime的索引,查询应该很快对吧?错。因为对字段做了函数运算之后,索引里存的是原始值,没法直接匹配函数结果,优化器只能放弃索引,老老实实全表扫。解决办法很简单,把函数运算挪到等号右边:where createtime >= '2024-01-01 00:00:00' and create_time < '2024-01-02 00:00:00'。这样索引就能正常走。类似的还有隐式类型转换,比如varchar字段和数字比较,你写where phone = 138000,数据库会把字段转成数字再比,索引又废了。
连接查询也得讲究策略。很多人写join的时候,小表驱动大表这个原则都听过,但实操起来就忘了。优化器虽然会自己调整执行计划,但你写的SQL结构会影响它的判断。更重要的是,join的字段必须两边都有索引,否则就是嵌套循环扫全表,那酸爽,谁用谁知道。另外,能用inner join就别用left join,left join有时候会让优化器犯迷糊,尤其在多表连接的时候。我之前排查过一个十二张表连接的报表查询,光left join就有八个,后来改成inner join加子查询预聚合,查询时间从二十秒降到了两秒。不是说你不能用left join,而是你得知道它背后的代价。
还有一个跟查询优化关系很大的点,容易被忽略:连接池和缓存。数据库查询再快,也快不过不查。把高频访问的热数据放到Redis里,或者用本地缓存扛住一部分读流量,数据库的压力小了,查询自然就快了。当然,缓存有缓存一致性的问题,得设计好失效策略,不然数据更新了,缓存还是旧值,用户看到的就是脏数据。另外,数据库连接池的大小也要合理设置,开太多连接反而因为上下文切换导致性能下降。有个经验值,连接数设成CPU核心数的两倍加磁盘数,差不多够用,具体还得压测调优。
说回最开头那个朋友的事故,我后来问他,你们平时写SQL有评审吗?他愣了半天说没有,都是开发自己写自己上线。这可能是比任何优化技巧都重要的一点:建立一套SQL规范和评审机制。比如强制要求所有查询必须走索引,不允许select *,不允许在索引列上做函数运算,复杂查询必须经过DBA审核。这些规则看起来像束缚,实际上是帮你兜底。好习惯养成之后,很多性能问题在开发阶段就被掐死了,根本轮不到线上出事再去救火。
数据库查询优化这事儿,说到底不是某一个技巧的功劳,而是一整套组合拳。索引、查询语句写法、分页策略、连接方式、缓存设计,每个环节抠一点,合起来就是几倍甚至十几倍的差距。你不需要成为数据库专家,只要把这些基础原则刻在脑子里,写SQL的时候多问自己一句:这一步能不能少扫点数据?这个查询能不能少回一次库?就这一句,能帮你躲掉百分之八十的性能坑。下次再遇到查询慢,别急着甩锅给数据库,先把你自己的SQL翻出来看看,大概率能找到问题。


