广告位联系
返回顶部
分享到

MySql深分页问题解决

Mysql 来源:互联网 作者:秩名 发布时间:2023-02-06 21:29:33 人浏览
摘要

1. 问题描述 日常开发中经常会涉及到数据查询分页的问题,一般情况下都是根据前端传入页数与页码通过mysql的limit方式实现分页,对于数据量较小的情况下没有问题,但是如果数据量

1. 问题描述

日常开发中经常会涉及到数据查询分页的问题,一般情况下都是根据前端传入页数与页码通过mysql的limit方式实现分页,对于数据量较小的情况下没有问题,但是如果数据量很大,深分页可能导致查询效率低下,接口超时的情况。

2. 问题分析

其实对于我们的 MySQL 查询语句来说,整体效率还是可以的,该有的联表查询优化都有,该简略的查询内容也有,关键条件字段和排序字段该有的索引也都在,问题在于他一页一页的分页去查询,查到越后面的页数,扫描到的数据越多,也就越慢。

我们在查看前几页的时候,发现速度非常快,比如 limit 200,25,瞬间就出来了。但是越往后,速度就越慢,特别是百万条之后,卡到不行,那这个是什么原理呢。先看一下我们翻页翻到后面时,查询的 sql 是怎样的:

1

select * from t_name where c_name1='xxx' order by c_name2 limit 2000000,25;

这种查询的慢,其实是因为 limit 后面的偏移量太大导致的。
比如像上面的 limit 2000000,25,这个等同于数据库要扫描出 2000025 条数据,然后再丢弃前面的 20000000 条数据,返回剩下 25 条数据给用户,这种取法明显不合理。

3. 验证测试

3.1 创建两个表

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

-- 创建两个表:员工表和部门表

-- 部门表,存在则进行删除

drop table if EXISTS dep;

create table dep(

    id int unsigned primary key auto_increment,

    depno mediumint unsigned not null default 0,

    depname varchar(20) not null default "",

    memo varchar(200) not null default ""

);

 

-- 员工表,存在则进行删除

drop table if EXISTS emp;

create table emp(

    id int unsigned primary key auto_increment,

    empno mediumint unsigned not null default 0,

    empname varchar(20) not null default "",

    job varchar(9) not null default "",

    mgr mediumint unsigned not null default 0,

    hiredate datetime not null,

    sal decimal(7,2) not null,

    comn decimal(7,2) not null,

    depno mediumint unsigned not null default 0

);

注意说明

  • mediumint是MySQL数据库中的一种整型,比INT小,比SMALLINT大,
  • 取值范围为:-8388608到8388607,无符号的范围是0到16777215。
  • 中等大小的整数,一位大小为3个字节。

3.2 创建两个函数

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

-- 创建两个函数:生成随机字符串和随机编号

-- 产生随机字符串的函数

delimiter $ 

drop FUNCTION if EXISTS rand_string;

CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255)

BEGIN

    DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmlopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';

    DECLARE return_str VARCHAR(255) DEFAULT '';

    DECLARE i INT DEFAULT 0;

    WHILE i < n DO

    SET return_str = CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));

    SET i = i+1;

    END WHILE;

    RETURN return_str;

END $

delimiter;

 

-- 产生随机部门编号的函数

delimiter $ 

drop FUNCTION if EXISTS rand_num;

CREATE FUNCTION rand_num() RETURNS INT(5)

BEGIN

    DECLARE i INT DEFAULT 0;

    SET i = FLOOR(100+RAND()*10);

    RETURN i;

END $

delimiter;

 

注意说明

-- 执行函数问题,This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its de

-- 这是我们开启了bin-log, 我们就必须指定我们的函数是否是,DETERMINISTIC 不确定的, NO SQL 没有SQl语句,当然也不会修改数据

-- 在MySQL中创建函数时出现这种错误的解决方法:set global log_bin_trust_function_creators=TRUE;

set global log_bin_trust_function_creators=TRUE;

3.3 编写存储过程

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

-- 编写存储过程,模拟 100W 的员工数据。

-- 建立存储过程:往emp表中插入数据

 DELIMITER $

 drop PROCEDURE if EXISTS insert_emp;

 CREATE PROCEDURE insert_emp(IN START INT(10),IN max_num INT(10))

 BEGIN

     DECLARE i INT DEFAULT 0;

     /*set autocommit =0 把autocommit设置成0,把默认提交关闭*/

     SET autocommit = 0;

     REPEAT

     SET i = i + 1;

     INSERT INTO emp(empno,empname,job,mgr,hiredate,sal,comn,depno) VALUES ((START+i),rand_string(6),'SALEMAN',0001,now(),2000,400,rand_num());

     UNTIL i = max_num

     END REPEAT;

     COMMIT;

 END $

 DELIMITER;

  

-- 插入500W条数据,时间有点久,耐心等待,1409s

 call insert_emp(0,5000000);

 

-- 查询部门员工表

select * from emp LIMIT 1,10;

3.4 编写存储过程

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

-- 编写存储过程,模拟 120 的部门数据

-- 建立存储过程:往dep表中插入数据

 DELIMITER $

 drop PROCEDURE if EXISTS insert_dept;

 CREATE PROCEDURE insert_dept(IN START INT(10),IN max_num INT(10))

 BEGIN

     DECLARE i INT DEFAULT 0;

     SET autocommit = 0;

     REPEAT

     SET i = i+1;

     INSERT  INTO dep( depno,depname,memo) VALUES((START+i),rand_string(10),rand_string(8));

     UNTIL i = max_num

     END REPEAT;

     COMMIT;

 END $

 DELIMITER;

  

-- 插入120条数据

 call insert_dept(1,120);

 

-- 查询部门员工表

select * from dep;

3.5 创建索引

1

2

3

4

5

-- 建立关键字段的索引,这边是跑完数据之后再建索引,会导致建索引耗时长,但是跑数据就会快一些。

-- 建立关键字段的索引:排序、条件

CREATE INDEX idx_emp_id ON emp(id);

CREATE INDEX idx_emp_depno ON emp(depno);

CREATE INDEX idx_dep_depno ON dep(depno);

3.6 验证测试

1

2

3

4

5

6

7

8

-- 验证测试

-- 偏移量为100,取25,Time: 0.011s

SELECT a.empno,a.empname,a.job,a.sal,b.depno,b.depname

from emp a left join dep b on a.depno = b.depno order by a.id desc limit 100,25;

 

-- 偏移量为4800000,取25,Time: 10.242s

SELECT a.empno,a.empname,a.job,a.sal,b.depno,b.depname

from emp a left join dep b on a.depno = b.depno order by a.id desc limit 4800000,25;

4. 解决方案

4.1 使用索引覆盖+子查询优化

因为我们有主键 id,并且在上面建了索引,所以可以先在索引树中找到开始位置的 id 值,再根据找到的 id 值查询行数据。

1

2

3

4

5

6

7

8

9

10

11

-- 子查询获取偏移100条的位置的id,在这个位置上往后取25,Time: 0.04s

 SELECT a.empno,a.empname,a.job,a.sal,b.depno,b.depname

 from emp a left join dep b on a.depno = b.depno

 where a.id >= (select id from emp order by id limit 100,1)

 order by a.id limit 25;

 

-- 子查询获取偏移4800000条的位置的id,在这个位置上往后取25,Time: 1.549s

 SELECT a.empno,a.empname,a.job,a.sal,b.depno,b.depname

 from emp a left join dep b on a.depno = b.depno

 where a.id >= (select id from emp order by id limit 4800000,1)

 order by a.id limit 25;

4.2 起始位置重定义

记住上次查找结果的主键位置,避免使用偏移量 offset。

这个效率是最好的,无论怎么分页,耗时基本都是一致的,因为他执行完条件之后,都只扫描了 25 条数据。

但是有个问题,只适合一页一页的分页,这样才能记住前一个分页的最后 id。如果用户跳着分页就有问题了,比如刚刚刷完第 25 页,马上跳到 35 页,数据就会不对。这种的适合场景是类似百度搜索或者腾讯新闻那种滚轮往下拉,不断拉取不断加载的情况。这种延迟加载会保证数据不会跳跃着获取。

1

2

3

4

5

6

7

8

9

10

-- 记住了上次的分页的最后一条数据的id是100,这边就直接跳过100,从101开始扫描表,Time: 0.006s

 SELECT a.id,a.empno,a.empname,a.job,a.sal,b.depno,b.depname

 from emp a left join dep b on a.depno = b.depno

 where a.id > 100 order by a.id limit 25;

 

-- 记住了上次的分页的最后一条数据的id是4800000,这边就直接跳过4800000,从4800001开始扫描表,Time: 0.046s

 SELECT a.id,a.empno,a.empname,a.job,a.sal,b.depno,b.depname

 from emp a left join dep b on a.depno = b.depno

 where a.id > 4800000

 order by a.id limit 25;

4.3 降级策略

看了网上一个阿里的 DBA 同学分享的方案:配置 limit 的偏移量和获取数一个最大值,超过这个最大值,就返回空数据。
因为他觉得超过这个值你已经不是在分页了,而是在刷数据了,如果确认要找数据,应该输入合适条件来缩小范围,而不是一页一页分页。

5. 梳理总结

深分页问题从理论上来说是存在的场景,但是从实际的业务场景考虑,深分页很多情况下缺少具体的业务场景做支撑,试想哪个业务会从480W页面,查询25条数据,如果需要搜索某条数据,使用最多的应该根据条件类型过滤吧。

每种方案各有优缺点,具体采用那种解决方案需要结合具体的业务场景,如果根据实际业务场景不需要深分页,可以采用降级策略,设置分页参数阈值。如果确实需要深分页问题可以覆盖子+子查询优化或者通过偏移量查询,如果能获取到偏移量的前提下优先选择偏移量的方案,否则采用覆盖索引+子查询。

无论是否深分页都应该考虑限流降级的问题,而且要考虑短时间内重复调用的问题,可以限制每秒执行次数,避免用户误点以及调用频繁带来的数据安全问题。


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://blog.csdn.net/m0_37583655/article/details/124385347
相关文章
  • MySQL字符串前缀索引使用

    MySQL字符串前缀索引使用
    1. 前缀索引与全部索引概念 怎么给字符串字段加索引?现在,几乎所有的系统都支持邮箱登录,如何在邮箱这样的字段上建立合理的索引,
  • MySql深分页问题解决
    1. 问题描述 日常开发中经常会涉及到数据查询分页的问题,一般情况下都是根据前端传入页数与页码通过mysql的limit方式实现分页,对于数据
  • MySQL之xtrabackup备份恢复的实现

    MySQL之xtrabackup备份恢复的实现
    mysql版本:8.0.28 xtrabackup版本:8.0.28 1、安装xtrabackup 下载地址:Download Percona XtraBackup 8.0https://www.percona.com/downloads 1 2 [root@myoracle ~]# tar -zxvf
  • Mysql的SELECT语句与显示表结构介绍

    Mysql的SELECT语句与显示表结构介绍
    SELECT... 1 2 SELECT 1+1, 2+2;# 直接这样写相当于下面这句 SELECT 1+1, 2+2 FROM DUAL; # 这里DUAL:伪表 SELECT ... FROM 语法: SELECT 标识选择哪些字段(列)
  • MySql如何不插入重复数据
    向数据库的插入值时,需要判断插入是否重复,然后插入。 这种操作怎么提高效率 下面说说一些解决方案 1、insert ignore into 当插入数据时,
  • MYSQL统计逗号分隔字段元素的个数
    写SQL的时候会遇到如下的问题,统计如下表中project_id字段中id的个数。 company_id project_id 77 94882,214880,94881,154882,94871,94879 140 2890,2872,3178,4314,
  • MySQL事务的SavePoint简介及操作
    什么是SavePoint SavePoint是数据库事务中的一个概念, 可以将整个事务切割为不同的小事务, 可以选择将状态回滚到某个小事务发生时的样子,
  • mysql查看表大小的方法介绍
    1.查看所有数据库容量大小 1 2 3 4 5 6 7 8 select table_schema as 数据库, sum(table_rows) as 记录数, sum(truncate(data_length/1024/1024, 2)) as 数据容量(MB), sum(
  • MySQL不用like+%实现模糊查询

    MySQL不用like+%实现模糊查询
    我们都知道 InnoDB 在模糊查询数据时使用 %xx 会导致索引失效,但有时需求就是如此,类似这样的需求还有很多,例如,搜索引擎需要根基用
  • SQL通用语法以及分类图文介绍
    MySQL的启动和连接数据模型 MySQL的启动和停止 1.services.msc 2.命令行启停 启动:net start mysql80 停止:net stop mysql80 (命令行窗口以管理员权限打
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计