数据库跑得慢,十个里有八个是SQL写得不讲究。这不是玄学,是实打实的逻辑问题——你让数据库干了一堆它本可以不干的活,它自然就给你脸色看。我见过太多开发同学,一遇到查询慢就急着加索引、扩内存,结果呢,索引建了一堆,查询还是慢得像蜗牛爬。折腾半天,发现就是一条子查询写得太蠢,改写成JOIN之后,速度直接提升了几十倍。所以,聊性能优化,咱得先摆正心态:SQL写得好,比什么都强。

先说说最基础也最容易被忽视的一点:SELECT后面别跟星号。很多人图省事,一把梭,觉得反正数据都要用。可问题是,数据库得把整张表的每一列都读出来,哪怕你只需要其中两三个字段。这就像你去超市购物,明明只买一瓶酱油,结果推着购物车把整个超市逛了一圈,时间全耗在路上了。更糟的是,如果表结构里有个大文本字段或者二进制字段,那这查询的代价直接翻倍。正确做法是只列出你真正需要的列名,让数据库少干活,它自然跑得快。
接下来是索引的事儿。索引不是越多越好,也不是随便建在哪个字段上都行。你得理解,索引的本质是空间换时间,它确实能加速查询,但每次写入、更新操作,索引都得同步维护,索引建多了,写入性能反而会下降。真正该加索引的,是那些高频出现在WHERE条件里、JOIN关联字段上、以及ORDER BY排序的列。还有个细节很多人容易踩坑:在索引列上做函数运算,比如,这会让索引直接失效,因为数据库没法对函数处理后的结果走索引。你得改成这样的范围查询,索引才能派上用场。
再说说分页查询。很多系统都有列表页,一页显示20条记录,数据量一大,分页就慢得让人抓狂。最常见的问题是这种写法,数据库得先扫描前十万条记录,然后扔掉,只取20条。这活儿干得冤枉啊。优化思路有两种:一种是记住上一页一条记录的ID,然后用来取下一页,这样数据库就能直接跳到目标位置,不用白跑十万条路。另一种是如果业务允许,用延迟关联,先查出主键ID,再去关联详细数据,也能有效减少扫描量。
子查询和JOIN的选择也是个经典话题。不是说子查询一定慢,但很多情况下,用JOIN代替子查询能明显提升效率。特别是那些相关子查询,也就是子查询里引用了外层表的字段,这种查询对每一行外层记录都要执行一次子查询,数据量一大,性能直接崩掉。比如你想找出每个分类下最新的一条记录,用相关子查询写,可能跑半天;但用窗口函数或者自连接,几秒钟就能出来。当然,JOIN也不是万能的,如果关联的两张表数据量差异巨大,或者关联字段没有索引,JOIN也可能变成灾难。关键是得看执行计划,别凭感觉。
说到执行计划,这玩意儿是SQL优化的照妖镜。很多人调优全靠猜,这里改改那里试试,效率极低。其实MySQL里一下,Oracle里一下,数据库就会告诉你它打算怎么执行这条SQL:全表扫描还是走索引?扫描了多少行?有没有排序?有没有临时表?这些信息比任何优化技巧都管用。学会了看执行计划,你就知道问题到底出在哪一步,是索引没建对,还是关联顺序不合理,还是该改写SQL逻辑。我建议每个写SQL的人都养成习惯,凡是慢查询,先EXPLAIN,再动手改。
还有一个场景特别容易被忽略:大批量插入数据。很多系统导入数据时,一条一条INSERT,循环几万次,慢得让人怀疑人生。其实优化空间很大,比如用批量INSERT,一次插入几百上千条,减少网络往返和日志刷写次数。或者干脆用LOAD DATA INFILE这种原生导入工具,速度能提升一个量级。另外,如果导入过程中不需要外键检查和唯一性校验,可以临时关闭这些约束,等数据导完再开启,也能省下大量时间。当然,这得在业务允许的前提下操作,别把数据搞乱了。
想说的是,SQL优化没有银弹,但有一条主线:让数据库少干活。不管是减少扫描的数据量、减少返回的字段、减少不必要的计算,还是让索引真正发挥作用,本质上都是在帮数据库减轻负担。你写的每一条SQL,都是你跟数据库之间的一次对话——你说得越精准,它回答得就越快。所以下次再遇到慢查询,先别急着骂数据库,回头看看自己写的SQL,是不是啰嗦了、绕路了、多余了。把SQL写利索了,性能自然就上去了。这活儿,靠的是细心和耐心,跟写代码一样,功夫在平时。


