MySQL 百万级数据的4种查询优化方式


Posted in MySQL onJune 07, 2021

一.limit越往后越慢的原因

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

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

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

MySQL 百万级数据的4种查询优化方式

二.百万数据模拟

1、创建员工表和部门表,编写存储过程插数据

/*部门表,存在则进行删除 */
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
);
/* 产生随机字符串的函数*/
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;
/*建立存储过程:往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;

/*建立存储过程:往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;

2.执行存储过程

/*插入120条数据*/
call insert_dept(1,120);
/*插入500W条数据*/
call insert_emp(0,5000000);

插入500万条数据可能很慢

三.4种查询方式

1.普通limit分页

/*偏移量为100,取25*/
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*/
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;

执行结果

[SQL]
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;
受影响的行: 0
时间: 0.001s
[SQL]
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;
受影响的行: 0
时间: 12.275s

越往后,查询效率越慢

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

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

/*子查询获取偏移100条的位置的id,在这个位置上往后取25*/
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*/
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;

执行结果

[SQL]
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;
受影响的行: 0
时间: 0.106s

[SQL]
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;
受影响的行: 0
时间: 1.541s

3.起始位置重定义

适用于主键是自增主键的表

/*记住了上次的分页的最后一条数据的id是100,这边就直接跳过100,从101开始扫描表*/
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开始扫描表*/
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;
[SQL]
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;
受影响的行: 0
时间: 0.001s

[SQL]
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;
受影响的行: 0
时间: 0.000s

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

4,降级策略(百度的做法)

这个策略是最简单有效的,因为一般的大数据查询都会有搜索条件,没人会关注100页以后的内容,当用户查询页数过大时,给它返回一个错误就行了,例如百度就只能搜索到76页

以上就是MySQL 百万级数据的4种查询优化方式的详细内容,更多关于MySQL 百万级数据查询优化的资料请关注三水点靠木其它相关文章!

MySQL 相关文章推荐
MySQL官方导出工具mysqlpump的使用
May 21 MySQL
mysql中between的边界,范围说明
Jun 08 MySQL
一篇文章带你深入了解Mysql触发器
Aug 02 MySQL
Mysql中where与on的区别及何时使用详析
Aug 04 MySQL
mysql 索引合并的使用
Aug 30 MySQL
MySQL命令无法输入中文问题的解决方式
Aug 30 MySQL
mysql分表之后如何平滑上线详解
Nov 01 MySQL
MySQL Innodb索引机制详细介绍
Nov 23 MySQL
你真的会用Mysql的explain吗
Mar 31 MySQL
MySQL数据库 安全管理
May 06 MySQL
mysql幻读详解实例以及解决办法
Jun 16 MySQL
MySQL count(*)统计总数问题汇总
Sep 23 MySQL
MySQL 全文检索的使用示例
Jun 07 #MySQL
MySQL 常见的数据表设计误区汇总
Jun 07 #MySQL
浅谈MySQL next-key lock 加锁范围
MySQL为id选择合适的数据类型
MySQL单表千万级数据处理的思路分享
Jun 05 #MySQL
MySQL 时间类型的选择
Jun 05 #MySQL
MySQL索引失效的典型案例
Jun 05 #MySQL
You might like
2020显卡排行榜天梯图 显卡天梯图2020年3月最新版
2020/04/02 数码科技
BBS(php &amp; mysql)完整版(四)
2006/10/09 PHP
PHP生成网页快照 不用COM不用扩展.
2010/02/11 PHP
joomla jce editor 解决上传中文名文件失败问题
2013/06/09 PHP
PHP中Header使用的HTTP协议及常用方法小结
2014/11/04 PHP
PHP中的命名空间相关概念浅析
2015/01/22 PHP
PHP实现对文件锁进行加锁、解锁操作的方法
2017/07/04 PHP
Ajax中的JSON格式与php传输过程全面解析
2017/11/14 PHP
详解PHP变量传值赋值和引用赋值变量销毁
2019/03/23 PHP
TP5框架页面跳转样式操作示例
2020/04/05 PHP
laravel7学习之无限级分类的最新实现方法
2020/09/30 PHP
JavaScript实现统计文本框Textarea字数增强用户体验
2012/12/21 Javascript
Javascript基础教程之switch语句
2015/01/18 Javascript
JavaScript实现向OL列表内动态添加LI元素的方法
2015/03/21 Javascript
如何利用AngularJS打造一款简单Web应用
2015/12/05 Javascript
js获取鼠标位置实例详解
2015/12/09 Javascript
Node.js 中exports 和 module.exports 的区别
2017/03/14 Javascript
js is_valid_filename验证文件名的函数
2017/07/19 Javascript
移动端滑动切换组件封装 vue-swiper-router实例详解
2018/11/25 Javascript
Layui实现带查询条件的分页
2019/07/27 Javascript
html2canvas属性和使用方法以及如何使用html2canvas将HTML内容写入Canvas生成图片
2020/01/12 Javascript
Python中用Descriptor实现类级属性(Property)详解
2014/09/18 Python
分析Python编程时利用wxPython来支持多线程的方法
2015/04/07 Python
Python基于列表list实现的CRUD操作功能示例
2018/01/05 Python
python实现多线程网页下载器
2018/04/15 Python
matplotlib savefig 保存图片大小的实例
2018/05/24 Python
python实现简易数码时钟
2021/02/19 Python
详解python 利用echarts画地图(热力图)(世界地图,省市地图,区县地图)
2019/08/06 Python
python两种注释用法的示例
2020/10/09 Python
美国女士泳装店:Swimsuits For All
2017/03/02 全球购物
美国男士西装打折店:Jos. A. Bank
2017/11/13 全球购物
英国现代市场:ARKET
2019/04/10 全球购物
Electric官网:美国高级眼镜和配件品牌
2020/06/04 全球购物
西湖英语导游词
2015/02/06 职场文书
幼儿园六一儿童节主持词
2015/06/30 职场文书
如何书写公司员工保密协议?
2019/06/27 职场文书