数据库优化这事儿,说起来挺玄乎,好像只有大厂才配操心。其实不然,哪怕你手头就是个几千条数据的小项目,代码写得不讲究,照样能把服务器卡成PPT。我见过太多人,上来就奔着高深架构去,结果连个慢查询怎么排查都说不清楚。今天就聊聊我这些年踩过的坑,从最底层的表结构设计,到的SQL写法,。

先聊表结构。很多人设计表的时候,恨不得一个表塞进所有字段,字段类型全用VARCHAR(255),图省事。结果呢?数据一多,每行记录占的磁盘空间大得吓人,索引也没法高效利用。我有个朋友,数据库里存用户状态,明明就0和1两种值,愣是用VARCHAR存,后来改成TINYINT,表体积直接缩了三分之一。记住一个原则:能用数值就别用字符串,能用定长就别用变长。时间戳最好用整型存,比DATETIME省空间且查询快。还有,别滥用自增主键之外的索引。索引不是越多越好,每个索引写数据时都要更新,写入频繁的表,索引多了反而是负担。通常我给表加索引,只针对那些在WHERE、JOIN、ORDER BY里频繁出现的字段,而且优先考虑联合索引,把区分度最高的字段放在最前面。
再讲个常见的坑:冗余字段。有些人为了省去联表查询,把用户名字直接塞进订单表,理由是我每次都查用户名,联表太慢。这其实是个权衡。如果你的业务里,用户名几乎不变,那冗余一下没问题。但如果用户会改名字,你的订单表里那些冗余字段就全废了,还得写定时任务去刷。我建议的原则是:把频繁读取、极少更新的字段可以考虑冗余,但要控制数量。比如商品详情页需要展示价格、折扣、库存,这些数据变化不频繁,但查询量巨大,完全可以冗余在商品主表里。但千万别把用户的收货地址、手机号这种高频变更的字段也冗余进去,否则维护成本会让你崩溃。
架构层面,读写分离是很多团队的第一步。主库负责写,从库负责读,听起来简单,但坑也不少。比如主从延迟。你刚写入一条数据,用户刷新页面,从库还没同步到,看到的是旧数据,这体验很糟糕。解决办法有两个:一是关键业务强制读主库,比如支付成功后跳转订单页;二是引入缓存,先把数据写进Redis,读的时候优先走缓存,等主从同步完成再更新缓存。另外,从库的数量也不是越多越好。每个从库都会给主库增加复制压力,从库太多,主库的IO反而扛不住。我一般建议读写比例超过10:1才考虑加从库,而且从库之间最好做个负载均衡,别让其中一个被压垮。
说到缓存,这是提升查询效率的利器,但用不好就是灾难。很多人把缓存当万能药,所有查询都先查缓存,查不到再查数据库。问题是缓存的过期策略没想清楚,导致数据不一致。我见过最夸张的案例:一个电商系统,商品详情页的缓存过期时间设为24小时,结果运营改了价格,用户看到的还是旧价。正确的做法是:对于频繁变更的数据,使用主动失效策略。比如修改商品信息时,同时删除对应的缓存键,下次查询重新加载。对于热点数据,比如秒杀活动的商品列表,可以用定时刷新,每5秒从数据库拉一次最新数据写入缓存,既保证时效性,又避免缓存雪崩。另外,缓存穿透也得防。比如有人恶意查询一个不存在的ID,每次都会穿透到数据库。简单的方法就是在缓存里存一个空值,过期时间设短一点,比如30秒。
查询语句的优化,是大多数开发者的短板。很多人写SQL全靠直觉,结果一条语句拖垮整个库。最常见的问题就是不用索引。比如在WHERE条件里对索引字段做函数运算:WHERE DATE(createtime) = '2024-01-01'。这种写法会让索引失效,全表扫描。应该改成:WHERE createtime >= '2024-01-01' AND create_time < '2024-01-02'。还有,别滥用SELECT *,哪怕你只需要两个字段,也会把整行数据拉出来,增加网络传输和内存开销。用EXPLAIN分析执行计划是基本功,type字段如果出现ALL,就说明全表扫描了,必须加索引。另外,LIMIT分页也有技巧。传统的LIMIT 100,20会导致数据库扫描前面10万行,可以改成记录上次查询的最大ID,然后用WHERE id > 上次最大值 LIMIT 20,效率翻倍。
事务和锁的处理,也是优化重点。很多人为了图省事,把整个业务逻辑塞进一个大事务里,结果并发一高,死锁频发。比如一个订单创建流程,要扣库存、生成订单、更新用户积分,如果这三个操作都在一个事务里,而且表之间的锁顺序不一致,很容易死锁。解决方案是:尽量缩小事务范围,只把关键操作放在事务里,非核心逻辑放到事务外。比如扣库存和生成订单必须原子,但更新积分可以异步处理,用消息队列。另外,选择合适的锁策略。InnoDB默认行锁,但如果你的WHERE条件没用到索引,行锁会升级成表锁,性能直接崩。所以确保事务里的查询都走索引,否则不如不用事务。对于高并发场景,比如秒杀,可以考虑用乐观锁代替悲观锁,在更新时带上版本号,减少锁冲突。
说说慢查询日志。这是诊断问题的第一手资料。大多数数据库都默认开启慢查询日志,但很多人从来不看。我建议把慢查询阈值设得低一点,比如1秒,甚至500毫秒。然后定期分析日志,找出那些执行频率高、耗时长的语句。分析的时候,别只看执行时间,还要看扫描行数和返回行数的比例。比如一条语句扫描了10万行,只返回了10行,说明索引或者查询逻辑有问题。还有一种情况:同样的语句,白天跑没问题,晚上跑就慢,很可能是数据量大了或者统计信息没更新。定期执行ANALYZE TABLE更新统计信息,对查询优化器有帮助。另外,别迷信ORM自动生成的SQL。很多ORM框架为了通用性,生成的SQL效率很低,比如N+1查询问题。对关键业务,直接手写SQL并绑定参数,可控性高得多。
写到这里,你可能会觉得优化数据库是个无底洞。确实,没有银弹能解决所有问题。但核心思路其实就几条:设计阶段想清楚表结构和索引,架构层面做好读写分离和缓存,查询语句写规范,事务锁别滥用,靠慢查询日志持续迭代。每一条看起来都不难,难的是在项目初期就养成习惯。别等数据库卡死被老板骂了才想起优化,那时候已经晚了。从现在开始,拿起你的慢查询日志,找到那条最慢的SQL,动手改改。


