那天夜里两点半,线上接口报502。
这并非是首次出现这种状况,我朝着自己讲没什么问题。然而你瞧这CPU的曲线,它径直被拉到了满值然后冲破了上限。
我盯着那个慢查询日志,第二十三页了。凌晨两点,就我一个人。
该死的,LIMIT ,10这般情况,这是类似破面试宝典来的吧,把前面十万条数据全部扫描一遍之后丢弃掉,再拿出最后那十条,我真的服了。
群里消息开始炸。运维那哥们疯狂@我,在线等那种。
数据库卡死怎么办
实际上呢,就是limit深入得太过了。即便B+树再怎么厉害,也经不住你持续不断地往后翻阅呀。我问一下,去MySQL官方文档里的那句原本的话你还记得不——“要是要求访问的数据量是很少的那种情况,优化器会挑选辅助索引”,然而问题在于,当变大了之后那就不是少的状态了。
那怎么搞?换游标分页。
第一页正常查
FROM WHERE = 'U1001'
ORDER BY DESC LIMIT 20;
下一页用上次的最后ID
id, , FROM
WHERE id > 1000 AND = 'U1001'
ORDER BY DESC LIMIT 20;
改成这样,12秒转变为200毫秒,其原理是什么呢?索引运用where id>直接跳跃至定位点,无需从起始处扫描十万行。
群里问我怎么搞定的,我说”游标分页”。
没人理我。凌晨三点大家只想睡觉。
慢SQL优化思路
索引,真的是那种具有两面性的双刃剑。去年那次情况下,存储过程运行了足足130秒后还没能结束并被输出,这可把我吓得不轻,以至于我直接就进行了kill操作。当时那张表里面存在着差不多三十多万条数据,就是一个NOT IN子查询,竟然将整张表来来回回、反反复复地进行扫描。
而后,我历经思索终于明晰,NOT IN 这般逐行实施对比运算的书写方式,于数据量庞大之际,全然等同于纯粹的灾祸。
最终的解决办法是,去构建一个涵盖查询所需要的全部字段的覆盖索引,通过这个索引,能够直接使得数据库在索引树中就把对应的数据读取完毕,甚至连回表操作所需要的IO都可以省去了。这样一来,最终的执行时间能够直接降低到1秒以内,实现了100倍的提升幅度。
这就是覆盖索引的威力,数据全在索引里。
MySQL批量删除优化
还有一处存在问题之地。还记得那次在网络环境中删除数据的情况吗?我直接书写了一条 FROM logs WHERE < '"2024-01-01'这样的语句,运行起来差不多持续了将近十分钟却依旧没有结束。
何解?存在长事务,伴有大锁,致使出现爆炸情况。所有的变更均需写入二进制日志,如此一来,主从同步直接延迟到极为离谱的程度!
工业级解法是分批+limit+order by+延迟:
# 分批删,每批一万条,间隔1秒钟休息
logs SET = ''
WHERE != '' LIMIT 10000;
(); -- 拿到上一条SQL影响的行数
把少量行进行每次锁定,这种方式是足够安全的。挑出下一批之后,配合着sleep一点时间再去运行。
千万级数据查询优化
并非表越大就越好,线上那张用户表快要达到两千万了,每次进行 COUNT()操作时,都仿佛处于渡劫状态。
我的优化三板斧:
分区表把历史数据按年份隔离,查询只扫单分区
汇总表把日活用户相关数据每天算一次,即时查询不碰明细表
垂直分表把不常用的大字段单独拆出去,主表保持瘦身
若在这些开展之前,先行考量索引结构以及业务分区,这才是正确的道路,硬件升级乃是最后的办法。一旦单表数据超过一亿,那就肯定要进行分库分表了。
高并发读写分离
我们开展了一项涉及一个主库以及两个从库的操作,Mycat中间件发挥读写分离功能,所有的操作都走从库,、、操作均走主库。
主从延迟怎么办?
最可靠的举措是,对于所有有着强一致性要求的读操作,要明确地添加上@注解,以此强制使其走主库,进而保证能够读取到最新的数据。要是不存在这样的要求,那就再前往从库去分担压力。
效果显著,主库的CPU从85%下降至40%以下,读请求扩散出去后,数据库使其不容易被压垮了。
还有,连接池不要开得过大。情况有一种 ,配置时 设置成了1000 ,之后出现的事是 ,连接风暴把数据库堆到死机了。唯有合理配置方为真理。
在持续书写的进程里,天色随后逐渐变得明亮起来了。代码被递交给上级处,于此同时的是,监控图之上的那一根线条最终缓缓下降了下来。
如今再去回看那些坑,MySQL 将偕你同踩且能教你自行站起来。于那看似毫无解决办法的 limit 分页所处之地、于各类因隐式类型转换致使索引受损的细微之处、于事务隔离级别选差所设的陷阱当中,它会悄然向你揭示怎样去读懂执行计划、怎样令优化器为你效力。
最为巧妙的是,每行经过优化之后的源码,都在论证着一件事情,那就是,我们并非是在与数据库进行战斗,而是在跟昨天那一个尚不够良好的自己握手言实现和解。

