sql在不添加索引的前提下下的解决思路

@TOC


场景#

目前有一个 sql,需要查出某个客户的前 100 次消费记录,订单表有几亿数据,且只有主键 id 自增索引,不能改造表结构。如何解决?需要从数据库层面与非数据途径两方面给出两种解决方案。sql 如下:
select * from order where user_id = 1 order by created_time desc limit 100


一、数据库优化法#

1.、 第一步优化:#

将 order by created_time 替换为 order by desc,原因为只有 id 有索引。
看一下区别:
看一下 order by created_time

在这里插入图片描述
可以看到题目给的 sql 是超级慢的,全表所描 + 文件排序,上亿级别数据几分钟都执行不完。

看一下 order by id desc

在这里插入图片描述
可以看出是使用到索引的,且没有 file sort,一定要避免 file sort,超级慢的原因之一就是文件排序

2、 第二步优化:#

直接使用 where user_id = 112233 order by id desc limit 10,虽然比原 sql 强,但由于使用了排序,依然会慢。
这时我们可以考虑利用 id 主键索引,从后往前查,先查到这个用户的最后一笔订单的 id,然后使用 where id < xxx and user_id = 112233。
在这里插入图片描述

3、 试一下效果:#

找了一张 300 万数据的表:
在这里插入图片描述
原 sql 处理速度 where user_id = 112233 order by created_time desc limit 1:
在这里插入图片描述
第一次优化:
where user_id = 112233 order by id desc limit 1
在这里插入图片描述
使用最大的 id,查出剩余 9 条最新消费订单数据 WHERE id < xxx AND user_id = 112233 LIMIT 9:
在这里插入图片描述

二、非数据库优化法,使用 redis#

1、 思路#

可以使用 redis 的 list 结构实现队列,为每个用户实时维护前 N 笔消费的 order_id
redis 结构:key:user_id value:list 结构,存储 order_id
在存储 order_id 时需要注意并发的情况,多个用户同时下单,操作 redis 可能导致多插,考虑使用 redis 事物 + 乐观锁。redis 事物可以保证一致性与隔离性,但由于没有回滚机制,不能保证原子性,可以使用乐观锁来弥补。

2、 伪代码#

//cas 乐观锁变量
boolean cas = false;
// 失败后自旋次数
in i = 0;
// 允许自旋 10 次
while(i <= 10){
if(
// 使用 lua 脚本,并开启 redis 事务
// 开启事物
multi
// 存储最新的 10 笔订单号
redis.call(
if (redis.call(‘llen’, user_id) >10
then redis.call(‘rpop’, user_id)
else redis.call(‘rpush’, user_id,order_id)
)
// 提交事物
exec
) 为真,cas = true
}
if(cas === false){
// 异常记录,并发太大,需要继续调优
}

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。