我删了一行SQL,公司差点给我发律师函
别慌,不是真的发律师函。但那天CTO看我的眼神,确实像在看一个准备跑路的员工。
事情是这样的。那是个普通的周四晚上,我正准备下班享受周末,突然监控报警:订单服务响应时间暴涨20倍,大量请求超时。看了眼日志,DB连接池耗尽。一查原因,某位同事白天加了段「看起来无害」的查询,没有加索引,拖垮了整个系统。
我的处理方式很简单粗暴:先临时禁用那个查询涉及的接口,等下周一再处理。但CTO问我能不能根本解决,我脱口而出:「加个索引就行,一行SQL。」然后我打开Navicat,连上生产库,准备执行。
就在这时,CTO按住了我的手。
你知道生产环境执行SQL意味着什么吗?
很多初中级工程师可能不理解:在生产库执行DDL(Data Definition Language,比如ALTER、CREATE、DROP)是有风险的。MySQL 5.6之前,ALTER TABLE会锁表,线上服务直接hang住。即使用了新版本,也可能导致主从延迟、binlog膨胀、缓冲池污染等问题。
更重要的是:没有回滚预案的操作就是裸奔。
那天我运气好,CTO拦住了我。但不是所有人都这么幸运。我见过有团队直接在生产库执行DROP TABLE的——备份?没做。binlog?没开。数据?没了。
所以今天我要讲的不是「如何加索引」这种烂大街的话题。我想讲讲作为一个后端工程师,你对数据库做的每一个操作,都应该在你的掌控之中。
数据库操作的安全规范(血泪教训版)
第一条:永远不要在生产库直接操作
这条听起来像废话,但80%的团队做不到。为什么?因为懒。或者因为「就加个索引,5秒钟的事」。
正确流程是什么?
1. 在测试环境验证SQL的正确性和执行时间
2. 确认有完整备份(不是上周的备份,是操作前的备份)
3. 准备回滚方案(保留原表结构、准备恢复脚本)
4. 在低峰期执行(通常是凌晨3点,别问我为什么知道)
5. 执行后持续监控,保留操作记录
我知道有人会说「我们有主从,切换一下就行」。但主从切换本身就是个高风险操作,而且很多公司的从库同步延迟堪忧,切换了可能数据不一致。
第二条:DDL操作要用pt-online-schema-change
如果你用MySQL,Percona Toolkit的pt-osc是标配。它的工作原理是:创建新表→逐步迁移数据→替换原表,整个过程不会锁表。
pt-online-schema-change --alter "ADD INDEX idx_xxx(xxx)" D=test t=orders --execute
但别以为用了pt-osc就万事大吉。迁移过程中会占用额外磁盘空间(通常是原表1.5-2倍),对CPU和IO也有压力。有次我给一个2亿行的大表加索引,pt-osc跑了3天,期间监控报警不断。
所以第三个建议是:大表操作前,先估时间和资源。可以用这个公式估算:
预估时间 = (表大小 / 迁移速度) * 迁移批次
# 一般单次迁移1000-5000行,太多了会锁表,太少了效率低
第三条:每一次操作都要可追溯
我见过太多团队,数据库被改了n次,但没人知道是谁改的、为什么改、什么时候改的。这种情况下一旦出问题,只能靠猜。
解决方案:用SQL审核平台。现在开源的比较成熟的有Yearning、Archery、SQLE等。这些平台可以做到:
- SQL语法检查和优化建议
- 执行必须走审批流程
- 记录每次操作的执行人和执行时间
- 支持回滚(基于binlog解析)
没有审核平台怎么办?至少建个Excel表,记录:操作人、操作时间、SQL内容、执行原因、回滚方案。这总比什么都没有强。
关于连接池,你可能一直理解错了
回到开头那个事故。最终我是怎么解决的?加索引确实解决了问题。但更重要的是,我们重新审视了整个服务的数据库连接池配置,发现了更严重的问题。
连接池大小应该设多少?很多人会告诉你「看你CPU核心数,8核就设8*2=16」或者「根据并发量来设」。这些说法对,但不够精确。
更精确的公式是:
最佳连接数 = ((核心数 * 2) + 磁盘数) * 预估QPS / 1000 + 备用容量
但这个公式也不是万能的。关键参数是预估QPS和单Query的平均执行时间。
假设你的数据库单Query执行时间是10ms,那一个连接每秒最多处理100个请求。如果你的QPS是1000,理论上需要10个连接。但如果你的应用服务器有10台,每台10个连接就够了。
但很多人犯的错误是:把连接池设得太大。
连接池过大的危害:
- 数据库端连接数过多,CPU context switch飙升
- 每个连接都占用内存,数据库内存压力增大
- 瞬时并发来的时候,大量连接同时竞争资源,反而更慢
连接池过小的危害:
- 请求排队,服务响应延迟
- 数据库资源浪费(连接数太少意味着很多查询是串行的)
那怎么找到最优值?我的经验是:先设一个保守值,然后压测观察。观察两个指标:
- 活跃连接数是否接近最大值(接近说明不够)
- 等待连接的线程数是否为0(为0说明够用)
如果活跃连接数长期在80%以上,等待线程数>0,就该加连接池大小。如果这两个指标长期在20%以下,说明设大了,可以适当缩小。
最后的忠告
写这篇文章的时候,我想起了那个差点被开除的夜晚。CTO后来跟我说,他不是怕我删数据,他是怕我养成「直接操作生产库」的习惯。这种习惯迟早会出问题,出问题的时候,往往是不可挽回的那种。
后端工程师最值钱的品质是什么?我认为是敬畏心。对生产环境的敬畏,对数据的敬畏,对每一个可能影响用户操作的敬畏。
你可以在测试环境横着来,但生产环境,多小的一个操作都要有backup和rollback plan。这不是胆小,这是专业。
下次有人跟你说「就加个索引,5秒钟的事」,你可以把这篇文章甩给他。
完。