告警中发现接口响应时间从200ms变为12秒,CPU达到90%
问题产生的原因竟然是:分页查询导致的。
这里产生的原因竟然是:MYSQ深度分页的典型问题,--数据越往后,速度越让人抓狂。
本质原因就是:传统的分页机制,在数据洪流下的失效。
limit 100000 ,10,这样的查询会让数据库像逐页翻阅文档的抄写员,机械的扫描前10万条数据在丢弃。
当数据达到千万级别的时候:这种暴力扫描不仅仅会造成IO资源的巨大的浪费,更会导致业务关键查询的阻塞。
1.深度分页
我们的分页语句一般都这么写:
select * from orders where user_id='haha' order by created_time desc limit 0,20
当用户查询第1000页订单(每页20条),常见的分页写法如下:
select * from orders where user_id='haha' orederby create_time desc limit 19980,20;
执行流程解析:
1.使用联合索引idx_userid_created_time,读取19980+20条数据,
2.利用索引在内存中排序
3.丢弃19980数据,返回剩余的20条。
随着页码的增加,需要处理的数据会成线性的增加,当offset达到10w时候,查询耗时,会显著增加,达到100w时候,甚至需要数秒。
2.游标分页:
使用场景:支持连续分页(如无限滚动)
实现原理:基于有序且唯一的主键(如自增主键ID),通过记录上一页最后一条记录的标识,(如主键ID),将where条件与索引结合,跳过已查询数据。
第一页:
select * from order where user_id='haha' orederby created_time desc limit 20;
后续页:
select id,user_id ,amount from orders where id>1000 and user_id ='haha' order by created_time desc limit 20;
索引树直接定位到order_id=1000,的叶子结点,仅仅扫描后续1000条记录,避免遍历前面100万行数据。
优势:完全避免了offset 扫描,时间复杂度从O降低为1
天然支持顺序分页场景,(如无限滚动加载)
限制:不支持随机跳页,(如跳页到1000页)
需要保证排序字段唯一且有序。
3.延迟关联
实现原理:通过子查询,先获取主键范围,在关联主表获取完整的数据,减少回表的次数,利用覆盖索引优化性能。
select t1.* from oders t1 inner join(select id from orders where user_id ='haha' order by created_time desc limit 100000 20)t2 on t1.id =t2.id;
优势:
子查询:仅仅扫描索引树,避免回表开销。
主查询:通过主键精确匹配,效率极高
性能提高10倍。
覆盖索引优化:
实现原理:创建包含查询字段的联合索引,避免回表操作,例如:索引设计为(user_id,ID,created_time,amount)
分区表
实现原理:将大表,按照时间或者哈希值水平拆分,例如按照月份,每个分区独立存储,缩小扫描范围。
预计算分页技术:
实现原理:通过异步任务预生成分页数据,存储到Redis或物化视图,适合数据更新频率低的场景。
实现步骤:
1.定时任务生成热点页数据,
2.存储到Redis有序集合。
查询的时候直接获取缓存数据。
浙公网安备 33010602011771号