上个月凌晨3点,我盯着屏幕上一行鲜红的报错,手边的咖啡已经凉透了。生产环境的MySQL主库,因为一个没加索引的慢查询,直接把CPU打到了100%,整个后台瘫痪了11分钟。老板在群里@了我三次,客服电话被打爆。那一刻,我真想执行rm -rf /然后关机走人。
但冷静下来之后,我发现自己踩过的坑远不止这一回。从最早用SQLite当生产库被写爆,到MongoDB不加分片键导致全表扫描,再到Redis缓存穿透让数据库直接宕机——前后换了4种数据库,经历了6次线上事故,才慢慢摸出点门道。
先说最痛的。去年接手一个二手项目,用的是MySQL 5.7,innodb_buffer_pool_size设成了默认的128M。服务器32G内存,你敢信?业务量一上来,磁盘IO直接拉满,查询慢得像在爬。我查了慢查询日志才发现,有个联表查询走了全表,扫描了800万行数据。加了个联合索引之后,查询时间从12秒降到了0.03秒。这400倍的差距,就是索引和没索引的区别。
后来学乖了,每次建表之前先在本地用explain跑一遍执行计划。但你以为这样就稳了?太天真了。
有一次做数据迁移,从MySQL同步到Elasticsearch,用的阿里云的DTS。同步了三天三夜,结果最后对账发现差了2万条记录。排查了半天,发现是源表里有个字段类型是datetime,DTS默认转成了timestamp,时区没对齐,导致部分数据被当成重复记录过滤掉了。从那以后,任何数据迁移任务我都要求两边字段类型完全一致,迁移完必须跑一遍count(*)对账脚本。
再说说Redis的坑。项目初期缓存策略很简单——查DB之前先查缓存,缓存没有就查DB然后回写。看起来没啥问题对吧?直到某天运营搞了个秒杀活动,瞬间流量把Redis打满了,缓存大批量过期。所有请求直接穿透到数据库,MySQL连接数瞬间飙到2000,直接挂了。事后复盘,我加了两个东西:一是布隆过滤器拦截根本不存在的数据,二是限流组件,单机QPS超过500就直接返回降级页面。现在Redis的缓存命中率稳定在97%以上,数据库压力降了80%。
MongoDB也让我栽过跟头。当初选它是因为文档型存JSON方便,不用维护表结构。但忽略了最重要的一点——MongoDB的分片键决定了查询性能。我图省事,用了默认的_id做分片,结果业务查询全是用userId。每次查询都要广播到所有分片,慢得令人发指。后来重建了分片集合,用userId做分片键,查询时间从5秒降到了50毫秒。
PostgreSQL倒是相对省心,但也不是没坑。有一次用pg_dump备份,默认格式是plain SQL,备份文件16G。恢复的时候直接卡死了,因为单个事务太大,WAL写爆了磁盘。后来改用custom格式,加上-j 4并行恢复,速度快了3倍。
总结几条血的教训:
第一,数据库选型别跟风。关系型就老老实实用MySQL或PostgreSQL,非关系型根据查询模式选。如果主要是KV查询,用Redis;如果是文档搜索,用Elasticsearch;如果是时序数据,用InfluxDB。别想着一个库打天下。
第二,索引不是越多越好。有个同事给一张表加了15个索引,写入性能直接崩了。一般单表索引不超过5个,联合索引要考虑最左前缀原则。
第三,备份恢复一定要演练。只在文档里写“每周全量备份”没用,你得真试一次恢复。我见过有人备份文件损坏了半年都没发现,真要恢复的时候才发现是个空文件。
第四,连接池大小要压测。不是越大越好。MySQL官方建议连接数不超过CPU核心数的2倍,超过这个值反而会因为上下文切换导致性能下降。
第五,慢查询日志必须开。long_query_time设成1秒,定期分析。很多坑都是从一条慢查询开始的。
现在我的服务器上跑着5个数据库实例,每个都配了监控告警。慢查询超过1秒自动发钉钉,连接数超过80%自动扩容,磁盘空间低于20%自动清理归档。三个月没出过事故了。
数据库这东西,说难不难,说简单也不简单。关键是要敬畏它,别觉得“跑起来就行”。每一个你没处理的边界情况,早晚都会在凌晨3点找你算账。
📎 延伸阅读
看完这篇,如果你想:
- 直接拿工具 → 回复”13“,我把跨境获客工具包发给你
- 系统学习 → 点击菜单”AI训练营”,从0开始跑通AI变现