
从数据处理入手:别让数据库拖后腿
后端开发天天跟数据打交道,而数据库绝对是性能问题的“重灾区”。我见过太多项目,代码逻辑写得花里胡哨,结果数据库查询没优化,接口响应时间直接飙到3秒以上。其实只要把数据库这关搞定,至少60%的性能问题都能解决。
先给数据库“建目录”:索引可不是随便加的
你去图书馆找书,肯定不会一本本翻吧?得先查目录。数据库里的索引就相当于目录,能让查询速度快几十倍甚至上百倍。但我发现很多新手要么忘了建索引,要么瞎建一堆,结果反而拖慢性能。
举个真实例子:去年帮朋友优化一个电商项目,用户下单后查询订单列表的接口要3秒多。我一看SQL,select from orders where user_id = ? order by create_time desc
,结果user_id
字段居然没建索引!用户表都100万数据了,数据库只能全表扫描,不慢才怪。后来加上普通索引,同样的查询直接降到200ms以内。但索引也不是越多越好,之前见过有人给表里每个字段都建索引,结果写入数据时,每次插一条记录要更新五六个索引,服务器CPU直接跑到90%。
到底该怎么建索引?记住三个原则:高频查询字段优先建(比如用户ID、订单号),区分度高的字段优先建(比如手机号比性别字段区分度高),避免给更新频繁的字段建过多索引(比如状态字段,每次更新都会触发索引重建)。如果你拿不准,可以用explain
命令看看查询语句有没有用到索引,没用到的话赶紧调整(具体怎么用explain
,可以参考MySQL官方文档的优化 )。
别让查询“贪多嚼不烂”:SQL语句要“瘦身”
除了索引,SQL语句本身写得烂也会拖垮性能。最常见的问题就是用select
,不管需不需要,把所有字段都查出来。我之前接手一个项目,有个接口查商品信息,select
把商品表20多个字段全返回了,包括商品详情这种大文本字段。结果每次查询要传输几KB甚至几十KB数据,带宽占用高不说,数据库读取也慢。后来改成只查前端需要的5个字段,接口响应时间直接少了一半。
还有就是分页查询,千万别用limit 100000, 10
这种写法。数据库会先查前100010条数据,再扔掉前100000条,效率极低。正确的做法是用“延迟关联”,先查主键ID,再用ID关联查其他字段,比如:
select a. from orders a inner join (select id from orders where user_id = ? order by create_time desc limit 100000, 10) b
on a.id = b.id
亲测这种方式比直接limit
快10倍以上,尤其数据量大的时候。
数据库连接池:别让“握手”浪费时间
你可能没注意过,后端程序跟数据库建立连接是很耗时的——三次握手、权限验证、资源分配,一套下来要几百毫秒。如果每次接口请求都新建连接,并发一高,光建立连接就能把服务器累死。这时候连接池就派上用场了,它会提前创建好一批连接,请求来了直接用,用完放回池子里,不用每次重新建。
但连接池的配置也是门学问。我见过有人把连接池最大连接数设成200,结果数据库服务器只有8核CPU,连接太多导致CPU上下文切换频繁,反而变慢。一般来说,连接池大小 设成 (CPU核心数 * 2 + 有效磁盘数),比如8核CPU配16-20个连接就差不多了。另外超时时间也得设,比如空闲连接超过30秒就关闭,避免连接一直占着资源不用。
不同索引类型怎么选?一张表帮你理清
索引类型 | 适用场景 | 优点 | 注意事项 |
---|---|---|---|
主键索引 | 唯一标识记录(如订单ID) | 查询速度最快,自动排序 | 一张表只能有一个,不能为空 |
普通索引 | 单字段查询(如用户手机号) | 灵活,不影响数据唯一性 | 避免对低频查询字段建索引 |
联合索引 | 多字段组合查询(如user_id+status) | 适配复杂查询条件 | 需要遵循“最左前缀原则”,字段顺序很重要 |
代码逻辑优化:写得快不如算得巧
数据库优化搞定后,接下来就得看看代码本身了。有时候不是数据量大,而是代码逻辑绕了远路,明明10行代码能搞定的事,非要写50行,还嵌套好几个循环,性能能好才怪。
选对“工具”:算法和数据结构别瞎用
我见过最离谱的代码:用List
存储10万条用户数据,然后每次判断用户是否存在时,都用list.contains(user)
——这可是O(n)的时间复杂度啊!10万条数据判断一次就要遍历10万次,100个并发请求过来,服务器直接卡死。后来改成HashSet
,判断存在性变成O(1),瞬间就不卡了。
这就是选对数据结构的重要性。后端开发常用的场景里,哈希表(Java的HashMap、Python的dict)适合频繁查询、判断存在性的场景;链表适合频繁增删的场景;数组适合需要随机访问的场景。如果你搞不清用哪个,可以先问自己:“这段代码最频繁的操作是什么?查得多还是改得多?”
算法也是同理。比如处理订单数据时,要根据用户ID分组统计金额,新手可能会用双重循环:外层遍历所有订单,内层遍历用户列表找匹配的ID。5000条订单数据,这样跑下来要10分钟。但如果用哈希表存用户ID和金额的映射,一次循环就能搞定,10秒都用不了。记住:能用O(n)解决的问题,就别用O(n²),数据量一大,差距能差出几百倍。
别让循环“原地打转”:少嵌套、多“偷懒”
循环是代码里最容易藏坑的地方,尤其是嵌套循环。我之前维护过一个报表接口,要统计每个用户的订单金额和商品数量,结果写了个三重循环:外层遍历用户,中层遍历订单,内层遍历商品。数据量稍微大一点,接口直接超时。后来重构时,先把所有订单按用户ID分组,再遍历分组后的数据统计,循环次数从“用户数×订单数×商品数”降到“订单数+用户数”,性能直接提升100倍。
还有个小技巧:循环里别做重复计算。比如for (int i = 0; i < list.size(); i++)
,每次循环都会调用list.size()
,如果是链表,size()
方法可能要遍历整个链表。改成int size = list.size(); for (int i = 0; i < size; i++)
,虽然代码多了一行,但性能能提升不少,数据量大的时候更明显。
热点数据“缓存”起来:别老麻烦数据库
后端接口里总有一些数据访问频率特别高,比如首页轮播图、热门商品列表、用户权限配置。这些数据如果每次都查数据库,不仅慢,还会给数据库增加压力。这时候缓存就是“救星”——把数据存到内存里,下次直接从内存取,速度能快几十倍。
我之前做的一个项目,商品详情接口每天有10万次请求,每次都查数据库,服务器CPU经常跑到80%以上。后来用Redis缓存热门商品的详情,过期时间设10分钟,结果接口响应时间从500ms降到50ms,数据库压力大减,服务器负载直接降到30%以下。不过缓存也有坑,比如“缓存穿透”——查一个不存在的数据,缓存没命中,每次都查数据库。这时候可以缓存空值,或者用布隆过滤器先过滤掉不存在的key。
另外缓存更新策略也很重要。如果数据更新不频繁(比如商品分类),可以用“定时更新”,每天凌晨刷一次缓存;如果更新频繁(比如订单状态),就用“更新数据库后主动更新缓存”,避免缓存和数据库数据不一致。
其实代码优化就像给房间收拾卫生,不一定非要换大房子(升级服务器),有时候把杂物归归类(优化数据结构)、扔掉没用的东西(精简查询字段)、常用的物品放顺手的地方(缓存热点数据),房间立马就宽敞了。下次你遇到接口慢的问题,不妨从数据库查询、循环逻辑、缓存这几个方向排查一下,说不定改几行代码,性能就能翻几倍。对了,改完记得压测一下,别凭感觉说“快了”,用数据说话才靠谱。