![]()
你的数据库只有3个用户在线,CPU却飙到90%。这种场景不是段子,是过去20年里无数开发者的日常。2024年Stack Overflow调研显示,数据库性能问题仍占后端故障的34%,而优化手段其实早写在文档里——只是没人按顺序读。
索引不是越多越好,是越准越好
索引(Index)是SQL加速的默认答案,但误用索引比不用更惨。我见过一个电商表建了47个索引,写入速度掉到原来的1/8。
单列索引的基础用法很直接:给email字段加索引,查询时跳过全表扫描。
CREATE INDEX idx_email ON users(email);
但真实业务很少单条件查询。用户要查"某邮箱2024年后注册",复合索引(Composite Index)才是正解——顺序决定生死,必须是email在前、created_at在后。
CREATE INDEX idx_email_created ON users(email, created_at);
代价也真实存在:每个索引都是写操作的税负,也是磁盘空间的租客。监控工具里看到索引命中率低于95%,就该考虑 pruning(剪枝)了。
SELECT * 是性能黑洞
偷懒写SELECT *就像搬家时把整栋楼塞进货车,只为取一件外套。带宽和内存在这行代码里无声燃烧。
对比这两行:
-- 浪费版:拉取所有字段再丢弃
SELECT * FROM users WHERE created_at > '2025-01-01';
-- 精确版:只取需要的email
SELECT email FROM users WHERE created_at > '2025-01-01';
数据量过百万时,这个习惯能差出10倍延迟。更隐蔽的问题是ORM(对象关系映射)框架的默认行为——很多开发者甚至没意识到自己发出了SELECT *。
![]()
把过滤留给数据库,别自己写循环
在应用层过滤数据是新手经典错误。SQL引擎用C++写了几十年的优化器,不是让你用Python for-loop复刻的。
低效模式:先SELECT *全表,再if判断日期。
高效模式:WHERE子句直接过滤。
SELECT * FROM users WHERE created_at >= '2026-01-01';
背后的执行路径完全不同。前者把整表数据拖过网络,后者让数据库用索引直接定位。PostgreSQL的EXPLAIN(执行计划分析)会显示"Seq Scan"还是"Index Scan"——看到前者就该警觉。
EXPLAIN是你欠下的技术债
每个主流数据库都内置了查询计划查看器,但使用率不到15%。这相当于买车从不看油耗表。
MySQL、PostgreSQL、SQL Server都支持:
EXPLAIN SELECT * FROM users WHERE email = 'alice@example.com';
输出里的"Seq Scan"意味着全表扫描,"Index Scan"才是健康状态。有个团队曾花两周优化代码,最后发现只是缺个索引——EXPLAIN 30秒就能定位。
更高级的版本是EXPLAIN ANALYZE,会显示实际执行时间和行数估计的偏差。当估计值和实际值差10倍以上,说明统计信息过期,需要ANALYZE TABLE。
N+1查询:ORM时代的瘟疫
这个问题在ORM普及后变本加厉。代码看起来优雅,发出的SQL却灾难。
![]()
场景:取100个用户,再逐个查他们的订单。
结果:1条查询变101条。 latency(延迟)从10ms涨到2秒。
解法是JOIN(连接查询)或预加载(Eager Loading)。Django的select_related、Rails的includes、SQLAlchemy的joinedload都是同一思路——用一次查询拿齐数据,而不是往返100次。
监控工具里看到"Queries per request"指标飙升,就该检查N+1。
分页不是OFFSET越跳越慢
传统分页用OFFSET,翻页越深越惨。OFFSET 100000时,数据库要先扫过10万行再丢弃。
游标分页(Cursor-based Pagination)用上一页的最后ID做锚点:
SELECT * FROM users WHERE id > 100000 ORDER BY id LIMIT 20;
时间复杂度从O(n)降到O(1)。Twitter、Instagram的时间线都用这方案。代价是不支持随机跳页,但用户真的需要跳到第8473页吗?
批量操作:原子性和性能的平衡
循环里单条INSERT是性能自杀。批量写入能减少网络往返和事务开销。
但批量不是无限大。MySQL的max_allowed_packet默认4MB,PostgreSQL的max_bind_parameters有上限。实测找到甜点——通常是500-5000条一批。
UPSERT(INSERT ... ON CONFLICT)比先SELECT再INSERT更省一次查询。批量DELETE同理,用IN子句代替循环。
有个细节:批量操作锁表时间更长,高并发场景要拆小批次。这不是非黑即白,是 trade-off(权衡)的艺术。
2025年PostgreSQL 17把JIT(即时编译)编译器默认开启,某些查询快30%。但文档里加了一句:"如果你的查询本来就快,JIT会让它更慢。"优化没有银弹,只有测量、调整、再测量。你的慢查询,EXPLAIN看了吗?
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.