前两天和一个做后端的朋友吃饭,他吐槽说公司系统最近越来越慢,查个订单要等好几秒,老板已经在群里点名了。我问他数据库是不是没优化过,他愣了一下说:“数据库还要优化?不都是写SQL就行吗?”这大概是很多开发者的真实写照。数据库平时安安静静躺在服务器里,没人觉得它是个事儿,但一旦访问量上来或数据量膨胀,它就会成为整个系统的瓶颈。优化数据库不是玄学,也不是什么高深莫测的黑科技,说白了就是搞清楚数据怎么存、怎么查、怎么避免无用功。很多问题其实都出在基础设计上,比如表结构不合理、索引建得不对、SQL写得随心所欲。把这些坑填了,性能往往能翻个跟头。

先说索引优化,这绝对是最立竿见影的手段。有次我去一家电商公司做技术交流,他们后台有个统计报表页面,跑一次要十几分钟。我一看代码,查询语句里连 WHERE 条件都没索引,全表扫描了几百万行数据。后来在用户 ID 和时间字段上建了复合索引,查询时间直接降到 200 毫秒。索引就像书的目录,没有目录只能一页页翻,有了目录就能直接定位到那一页。但索引不是越多越好,有些人为了省事,每个字段都加索引,结果写入时索引维护成本飙升,反而拖慢了整体性能。一般来说,高频查询的字段、经常用于排序和分组的字段、以及外键关联字段,才值得建索引。还要注意索引顺序,如果查询条件里既有用户 ID 又有时间,复合索引的顺序应该是用户 ID 在前、时间在后,因为用户 ID 的区分度更高。另外,尽量避免在索引列上做函数运算,比如 WHERE DATE(createtime) = '2024-01-01' 这种写法会让索引失效,正确做法是改成范围查询。
SQL 语句的写法,很多人觉得只要结果对就行,性能无所谓。这种想法在数据量小的时候确实没事,但一旦数据量上到百万级,写法之间的差距能相差几个数量级。比如 SELECT 这个操作,能少用就少用。你只需要三个字段,却把几十个字段都读出来,不仅浪费网络带宽,还浪费数据库内存。更严重的是,某些数据库在 SELECT 时无法利用覆盖索引,必须回表查询,性能直接打折。还有子查询和 JOIN 的选择,很多人习惯用子查询,觉得逻辑清晰,但子查询在 MySQL 里往往性能很差,因为要重复执行多次。我之前优化过一个订单系统,原本用子查询查用户最新订单,跑了 5 秒,改成 JOIN + 窗口函数后,0.3 秒就出结果了。另外,IN 和 EXISTS 的选择也值得注意:如果子查询结果集很小,用 IN 更快;如果外层表很小,用 EXISTS 更合适。这些细节,平时写代码时多想一想,能省下不少性能优化的功夫。
表结构设计这块,很多人一开始就没想清楚。比如字段类型的选择,能用 INT 就别用 BIGINT,能用 VARCHAR(50) 就别用 VARCHAR(500)。有人觉得反正数据库支持,留大一点没关系,但数据量上来以后,这种“没关系”会变成大问题。我见过一个用户表,status 字段用 VARCHAR(20) 存了“正常”“禁用”“待审核”这种文本,明明用 TINYINT 就能解决,结果占用了好几倍的存储空间,查询时还得做字符串比较,慢得不行。还有 NULL 值的问题,虽然 MySQL 允许字段为 NULL,但 NULL 会让索引变得复杂,查询时还得额外处理。尽量给字段设置 NOT NULL 并给一个默认值,例如用户注册时间默认当前时间、状态默认 0。再说分表分库,很多人觉得数据量大了才需要,但业务一旦爆发式增长,等你发现慢的时候再分表,迁移成本高得吓人。提前规划好分表策略,比如按用户 ID 哈希分表、按时间分区,能省掉很多后期痛苦。
缓存策略是数据库优化的“外挂”。数据库再快,也快不过内存。很多系统的问题是:明明数据几个小时才变一次,每次请求都去数据库查。比如网站首页的推荐商品列表,用户访问一次查一次,数据库压力可想而知。加个 Redis 或本地缓存,把热点数据存起来,设置过期时间,能扛住 90% 以上的读请求。但缓存也不是万能的,缓存穿透、缓存雪崩、缓存击穿这几个坑必须小心。缓存穿透是指查询一个不存在的数据,结果每次请求都绕过缓存直接查数据库,解决方案是缓存一个空值或使用布隆过滤器。缓存雪崩是指大量缓存同时过期,导致请求全部落到数据库上,解决方案是给过期时间加一个随机值,避免同时失效。缓存击穿是指某个热点 key 过期,瞬间高并发请求打到数据库,解决方案是使用互斥锁或提前更新缓存。这些细节,做缓存时一定要考虑进去,不然加缓存反而可能把系统搞崩。
慢查询日志是数据库优化的“体检报告”。很多人不知道 MySQL 自带慢查询日志功能,打开后能记录所有执行时间超过阈值的 SQL 语句。我习惯把阈值设成 1 秒,然后定期分析日志。有一次帮客户优化系统,发现慢查询日志里有一条 SQL 执行了 30 秒,原来是一条关联了 6 张表的复杂查询,还用了 ORDER BY RAND()。 ORDER BY RAND() 会让数据库对所有行生成随机数再排序,数据量一大就是灾难。优化方案是先在程序里生成随机 ID,再查对应记录,性能直接提升几十倍。慢查询日志不仅能帮你找到问题 SQL,还能反映数据库的整体负载情况。如果慢查询越来越多,说明性能在恶化,必须尽快处理。另外,EXPLAIN 命令也很实用,它能告诉你一条 SQL 的执行计划,比如用了哪些索引、扫描了多少行。我每次写复杂查询之前,都会先用 EXPLAIN 跑一遍,确保没有走全表扫描。
硬件和配置层面的优化,很多人容易忽略。数据库性能瓶颈有时不在代码,而在服务器本身。比如磁盘 I/O,传统机械硬盘和 SSD 的读写速度差了一个数量级。我见过一个系统,每天大量写入操作,磁盘 I/O 利用率一直 100%,换成 SSD 后问题直接解决。还有内存配置,MySQL 的 innodbbufferpoolsize 默认只有 128 M,对于几 GB 数据来说根本不够,需要根据服务器物理内存调高,一般设置成物理内存的 70% 左右。连接数也是常见问题,默认最大连接数是 151,如果并发请求高,很容易出现连接排队甚至拒绝服务。不过也别盲目调高,连接数越多,上下文切换越频繁,反而可能更慢。合理做法是结合业务峰值,设置一个合适的值,并配合连接池使用。另外,定期做数据库维护也很重要,比如重建索引、更新统计信息,这些操作能让查询优化器做出更合理的执行计划。
想说,数据库优化不是一锤子买卖,而是一个持续迭代的过程。业务在变,数据量在涨,用户的访问模式也在变,今天有效的优化方案,半年后可能就过时了。我见过太多团队,一开始把数据库性能调得很好,然后就不再关注,直到线上出问题才手忙脚乱。最好的做法是把优化融入日常开发流程:每次上线新功能前评估一下对数据库的影响;每次发现慢查询,都记录下来形成知识库;每个季度做一次性能复盘,看看哪些查询变慢、哪些表数据量增长过快。长期坚持下来,数据库不会成为瓶颈,反而会成为系统的定海神针。别忘了,数据库优化的本质是让数据用最少的资源、最快的时间到达它该去的地方。这个目标听起来简单,但要做好,需要你对数据足够了解,对业务足够敏感,对技术保持敬畏。


