本文是关于mysql的相关知识教程讲解,一起聊聊如何解决mysql深分页的问题,以及mysql表大数据量的情况,附上最近的优化慢sql问题的案例伪代码,有需要的朋友自取。
一文教会你解决mysql深分页的问题
问题描述
先看看表结构:
CREATE TABLE `p2p_detail_record` (
`id` varchar(32) COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '主键',
`batch_num` int NOT NULL DEFAULT '0' COMMENT '上报数量',
`uptime` bigint NOT NULL DEFAULT '0' COMMENT '上报时间',
`uuid` varchar(64) COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '会议id',
`start_time_stamp` bigint NOT NULL DEFAULT '0' COMMENT '开始时间',
`answer_time_stamp` bigint NOT NULL DEFAULT '0' COMMENT '应答时间',
`end_time_stamp` bigint NOT NULL DEFAULT '0' COMMENT '结束时间',
`duration` int NOT NULL DEFAULT '0' COMMENT '持续时间',
PRIMARY KEY (`id`),
KEY `idx_uuid` (`uuid`),
KEY `idx_start_time_stamp` (`start_time_stamp`) //索引,
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='p2p通话记录详情表';
原因分析
首先先来回顾一下mysql 的相关知识点。
聚簇索引和非聚簇索引
● 聚簇索引: 叶子节点储存的是整行的数据。
● 非聚簇索引: 叶子节点储存的是整行的数据对应的主键值。
使用非聚簇索引查询的流程
● 通过非聚簇索引树,找到对应的叶子节点,获取到主键的值。
● 再通过取到主键的值,回到聚簇索引树,找到对应的整行数据。(整个过程称为回表)
回到这条sql为什么慢的问题上,原因如下:
limit语句会先扫描offset+n行,然后再丢弃掉前offset行,返回后n行数据。也就是说limit 100000,10,就会扫描100010行,而limit 0,10,只扫描10行。这里需要回表100010次,大量的时间都在回表这个上面。
方案核心思路: 能不能事先知道要从哪个主键ID开始,减少回表的次数
解决方案
通过子查询优化:
select *
from p2p_detail_record ppdr
where id >= (select id from p2p_detail_record ppdr2 where ppdr2 .start_time_stamp >1656666798000 limit 100000,1)
limit 2000
相同的查询结果,也是10W条开始的第2000条,查询效率为200ms,是不是快了不少。
标签记录法
标签记录法: 其实标记一下上次查询到哪一条了,下次再来查的时候,从该条开始往下扫描。类似书签的作用
select * from p2p_detail_record ppdr
where ppdr.id > 'bb9d67ee6eac4cab9909bad7c98f54d4'
order by id
limit 2000
备注:bb9d67ee6eac4cab9909bad7c98f54d4是上次查询结果的最后一条ID
使用标签记录法,性能都会不错的,因为命中了id索引。但是这种方式有几个缺点。
● 只能连续页查询,不能跨页查询。
● 需要一种类似连续自增的字段(可以使用orber by id的方式)。
实战案例
需求: 需要查询查询某一时间段的数据量,假设有几十万的数据量需要查询出来,进行某些操作。
分批查询(分页查询),设计深分页问题,导致效率较慢。
CREATE TABLE `p2p_detail_record` (
`id` varchar(32) COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '主键',
`batch_num` int NOT NULL DEFAULT '0' COMMENT '上报数量',
`uptime` bigint NOT NULL DEFAULT '0' COMMENT '上报时间',
`uuid` varchar(64) COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '会议id',
`start_time_stamp` bigint NOT NULL DEFAULT '0' COMMENT '开始时间',
`answer_time_stamp` bigint NOT NULL DEFAULT '0' COMMENT '应答时间',
`end_time_stamp` bigint NOT NULL DEFAULT '0' COMMENT '结束时间',
`duration` int NOT NULL DEFAULT '0' COMMENT '持续时间',
PRIMARY KEY (`id`),
KEY `idx_uuid` (`uuid`),
KEY `idx_start_time_stamp` (`start_time_stamp`) //索引,
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='p2p通话记录详情表';
伪代码实现:
//最小ID
String lastId = null;
//一页的条数
Integer pageSize = 2000;
List<P2pRecordVo> list ;
do{
list = listP2pRecordByPage(lastId,pageSize); //标签记录法,记录上次查询过的Id
lastId = list.get(list.size()-1).getId(); //获取上一次查询数据最后的ID,用于记录
//对数据的操作逻辑
XXXXX();
}while(isNotEmpty(list));
<select id ="listP2pRecordByPage">
select *
from p2p_detail_record ppdr where 1=1
<if test = "lastId != null">
and ppdr.id > #{lastId}
</if>
order by id asc
limit #{pageSize}
</select>
关于mysql深分页的问题就讲解到这里,翼速应用平台内有更多相关资讯,欢迎查阅!
我来说两句