MySQL百万级数据查询优化(mybatis插件PageHelper问题)

MySQL百万级数据查询优化(mybatis插件PageHelper问题)

Executor

Mybatis中所有的Mapper语句的执行都是通过Executor进行的,Executor是Mybatis的一个核心接口,其定义如下。从其定义的接口方法我们可以看出,对应的增删改语句是通过Executor接口的update方法进行的,查询是通过query方法进行的。

PageHelper性能分析

MySQL分页在表比较大的时候,分页就会出现性能问题,MySQL的分页逻辑如下:比如:select * from user limit 100000,10

它是先执行 select * from user 扫描满足这个SQL语句,拿到执行结果后, 一页一页的找到行号为100000的行,返回接下来的10行数据,出现性能问题的原因有两个:

它先全表扫描了,整个表,而不是扫描到了满足条件的数据就不扫描了,比如select * from user limit 1,10 这个,它不是扫描到满足条件的10行数据就完事了,而是扫描了整个表,然后从这个结果集中从上往下扫描,只到找到行号为1的后面10行数据,这里出现性能问题的原因
注意:查询条件字段没有索引会导致全表扫描
就在于MySQL寻找行号的逻辑是怎么寻找的,是不是像如果是像数组那样通过下标一步定位行号就不存在页码大小的问题了,但是MySQL不是一步到位的找到这个页码的,具体是怎么找到页码的感兴趣的可以去看MySQL的源码,我们能做的就是将MySQL的逻辑转换为直接定位数据的位置。

<select id="queryStudentListLikeName" parameterType="java.lang.String" resultType="com.entity.Student">
    select 
    <include refid="Base_Column_List" />
    from student s
    WHERE s.name LIKE '%${name}%' 
     order by id desc
  </select>

mybatis的 PageHelper 插件会在上面直接加上 limit 语句,源码如下:

public class MySqlDialect extends AbstractHelperDialect {
    @Override
    public String getPageSql(String sql, Page page, CacheKey pageKey) {
        StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
        if (page.getStartRow() == 0) {
            sqlBuilder.append(sql);
            sqlBuilder.append(" LIMIT ");
            sqlBuilder.append(page.getPageSize());
        } else{
            sqlBuilder.append(sql);
            sqlBuilder.append(" LIMIT ");
            sqlBuilder.append(page.getStartRow());
            sqlBuilder.append(",");
            sqlBuilder.append(page.getPageSize());
            pageKey.update(page.getStartRow()); 
        }
        pageKey.update(page.getPageSize());
        return sqlBuilder.toString();
    }

性能扩展(对limit分页问题的性能优化方法)

测试分析

  1. 直接用limit offset,rows 分页语句, 也是我程序中用的方法:
select * from student limit offset,rows

当起始页较小时,查询没有性能问题,我们分别看下从10, 100, 1000,10000,400000,800000开始分页的执行时间(每页取20条)

select * from student limit 10, 20   0.016select * from student limit 100, 20   0.016select * from student limit 1000, 20   0.047select * from student limit 10000, 20   0.094select * from student limit 400000, 20   3.229select * from student  limit 866613, 20    37.44

可以看出随着起始记录的增加,时间也随着增大, 这说明分页语句limit跟记录行的偏移量(起始页码)是有很大关系的.

像这种分页最大的页码页显然这种时间是无法忍受的。从中我们也能总结出两件事情:
limit语句的查询时间与起始记录的位置成正比
mysql的limit语句是很方便,但是对记录很多的表并不适合直接使用。

1.利用表的覆盖索引来加速分页查询
我们都知道,利用了索引查询的语句中如果只包含了那个索引列(覆盖索引),那么这种情况会查询很快。
因为利用索引查找有优化算法,且数据就在查询索引上面,不用再去找相关的数据地址了,这样节省了很多时间。另外Mysql中也有相关的索引缓存,在并发高的时候利用缓存就效果更好了。

在我们的例子中,我们知道id字段是主键,自然就包含了默认的主键索引。现在让我们看看利用覆盖索引的查询效果如何。

这次我们之间查询最后一页的数据(利用覆盖索引,只包含id列),如下

select id from student limit 866613, 20    0.2

相对于查询了所有列的37.44秒,提升了大概100多倍的速度

那么如果我们也要查询所有列,有两种方法,一种是id>=的形式,另一种就是利用join,看下实际情况:

//使用ID> =
SELECT * FROM student WHERE ID > =(select id from student limit 866613, 1) limit 20
另一种写法
//使用JOIN的方式
SELECT * FROM student a JOIN (select id from student limit 866613, 20) b ON a.ID = b.id

这2种写法的查询时间都很短,大概0.2s左右

2. 复合索引优化方法

例子如下:
数据表 student( id, name,code,age…) 就这很多个字段,或者每条数据占的内存很大.id为主键索引,code为索引.age为索引

OK ,看下面这条sql语句:

select id,name from student order by code desc limit 100,10;

基本上0.01秒就OK,再看下面的

select id,name from student order by code limit 90000,10;

从9万条开始分页,结果:8-9秒完成,这样是不会走索引(条件查询,分组和排序的字段添加索引才会走索引),优化如下:

select id from student order by id limit 90000,10;
select id,name from student where id>=(select id from student
order by id limit 90000,1) limit 10;

或者

select c.id,c.title from (select id from student order by id limit 90000,1)t 
left join student c on t.id=c.id limit 10;

因为id是主键索引,查询的时候使用到了索引就是非常快的。但是如果这样的语句呢?如下:

select code from collect where age=55 order by code limit 90000,10; 

执行下来,发现:很慢,用了8-9秒!age和code 做了索引了啊?怎么会慢呢?

通过explain解析查询语句可以看出,普通索引进行排序用limit取值,会产生重排序(extra 一栏 Using filesort) .
在这里插入图片描述

遵循执行原则

1.查询语句同时出现执行顺序为:Where, Group By, Having, Order by;
2.复合索引:遵循最左原则
比如(a,b,c),abc都是排好序的,在任意一段a的下面b都是排好序的,任何一段b下面c都是排好序的;

从前往后依次使用生效,如果中间某个索引没有使用,那么断点前面的索引部分起作用,断点后面的索引没有起作用
where a=3 and b=45 and c=5 .... 这种三个索引顺序使用中间没有断点,全部发挥作用;
where a=3 and c=5... 这种情况下b就是断点,a发挥了效果,c没有效果
where b=3 and c=4... 这种情况下a就是断点,在a后面的索引都没有发挥作用,联合索引没有发挥任何效果;
where b=45 and a=3 and c=5 ....全部发挥作用,abc只要用上了就行,跟写的顺序无关.

联合索引
对于上面的最开始的例子,我们创建联合索引index(age,code) ,先执行age排序,所以在联合索引的第一位. 现在再来执行上面的sql,只要0.0X秒

select id from student where age=55 order by code limit 90000,10; 

主键本身是有顺序的,所以对主键排序不会产生重排,效果同上.

select id from student where age=55 order by id limit 90000,10; 

完美解决了分页问题了。可以快速返回覆盖索引(id)就有希望优化limit , 按这样的逻辑,百万级的limit 应该在0.0x秒就可以分完。看来mysql 语句的优化和索引时非常重要的!

参考文章:https://blog.csdn.net/weixin_37598682/article/details/94547518

  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Mybatis是一款优秀的ORM框架,而PageHelper则是一款用于Mybatis的分页插件使用PageHelper可以方便地实现分页查询功能。具体使用方法如下: 1.在pom.xml文件中添加PageHelper的依赖。 2.在Mybatis的配置文件中添加PageHelper插件配置。 3.在Mapper接口中定义分页查询的方法,并使用PageHelper.startPage方法设置分页参数。 4.在Controller中调用Mapper接口中的分页查询方法,并将查询结果封装到PageInfo对象中返回给前端。 下面是一个示例代码: 1.在pom.xml文件中添加PageHelper的依赖: ``` <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.11</version> </dependency> ``` 2.在Mybatis的配置文件中添加PageHelper插件配置: ``` <plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <property name="helperDialect" value="mysql"/> <property name="reasonable" value="true"/> <property name="supportMethodsArguments" value="true"/> <property name="params" value="count=countSql"/> </plugin> </plugins> ``` 3.在Mapper接口中定义分页查询的方法,并使用PageHelper.startPage方法设置分页参数: ``` List<User> getUserList(@Param("pageNum") int pageNum, @Param("pageSize") int pageSize); ``` ``` PageHelper.startPage(pageNum, pageSize); List<User> userList = userMapper.getUserList(pageNum, pageSize); ``` 4.在Controller中调用Mapper接口中的分页查询方法,并将查询结果封装到PageInfo对象中返回给前端: ``` @RequestMapping("/getUserList") public PageInfo<User> getUserList(@RequestParam(defaultValue = "1") int pageNum, @RequestParam(defaultValue = "10") int pageSize) { List<User> userList = userMapper.getUserList(pageNum, pageSize); PageInfo<User> pageInfo = new PageInfo<>(userList); return pageInfo; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值