mysql left 数学原理,MySQL全面瓦解21(番外):一次深夜优化亿级数据分页的奇妙经历...

背景

1月22号晚上10点半,下班后愉快的坐在在回家的地铁上,内心想着周末的生活怎么安排。sql

忽然电话响了起来,一看是咱们的一个开发同窗,顿时紧张了起来,本周的版本已经发布过了,这时候打电话通常来讲是线上出问题了。数据库

果真,沟通的状况是线上的一个查询数据的接口被疯狂的失去理智般的调用,这个操做直接致使线上的MySql集群被拖慢了。性能优化

好吧,这问题算是严重了,下了地铁匆匆赶到家,开电脑,跟同事把Pinpoint上的慢查询日志捞出来。看到一个很奇怪的查询,以下多线程

1 POST domain/v1.0/module/method?order=condition&orderType=desc&offset=1800000&limit=500

domain、module 和 method 都是化名,表明接口的域、模块和实例方法名,后面的offset和limit表明分页操做的偏移量和每页的数量,也就是说该同窗是在 翻第(1800000/500+1=3601)页。初步捞了一下日志,发现 有8000屡次这样调用。dom

这太神奇了,并且咱们页面上的分页单页数量也不是500,而是 25条每页,这个绝对不是人为的在功能页面上进行一页一页的翻页操做,而是数据被刷了(说明下,咱们生产环境数据有1亿+)。 详细对比日志发现,不少分页的时间是重叠的,对方应该是多线程调用。函数

经过对鉴权的Token的分析,基本定位了请求是来自一个叫作ApiAutotest的客户端程序在作这个操做,也定位了生成鉴权Token的帐号来自一个QA的同窗。立马打电话给同窗,进行了沟通和处理。工具

分析

其实对于咱们的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条数据给用户,这种取法明显不合理。优化

32779de333a8b2ee786b021eef24cf1f.png

你们翻看《高性能MySQL》第六章:查询性能优化,对这个问题有过说明:

分页操做一般会使用limit加上偏移量的办法实现,同时再加上合适的order by子句。但这会出现一个常见问题:当偏移量很是大的时候,它会致使MySQL扫描大量不须要的行而后再抛弃掉。

数据模拟

那好,了解了问题的原理,那就要试着解决它了。涉及数据敏感性,咱们这边模拟一下这种状况,构造一些数据来作测试。

一、建立两个表:员工表和部门表

1 /*部门表,存在则进行删除*/

2 drop table if EXISTSdep;3 create tabledep(4 id int unsigned primary keyauto_increment,5 depno mediumint unsigned not null default 0,6 depname varchar(20) not null default"",7 memo varchar(200) not null default""8 );9

10 /*员工表,存在则进行删除*/

11 drop table if EXISTSemp;12 create tableemp(13 id int unsigned primary keyauto_increment,14 empno mediumint unsigned not null default 0,15 empname varchar(20) not null default"",16 job varchar(9) not null default"",17 mgr mediumint unsigned not null default 0,18 hiredate datetime not null,19 sal decimal(7,2) not null,20 comn decimal(7,2) not null,21 depno mediumint unsigned not null default 0

22 );

二、建立两个函数:生成随机字符串和随机编号

1 /*产生随机字符串的函数*/

2 DELIMITER $3 drop FUNCTION if EXISTSrand_string;4 CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255)5 BEGIN

6 DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmlopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';7 DECLARE return_str VARCHAR(255) DEFAULT '';8 DECLARE i INT DEFAULT 0;9 WHILE i

17

18 /*产生随机部门编号的函数*/

19 DELIMITER $20 drop FUNCTION if EXISTSrand_num;21 CREATE FUNCTION rand_num() RETURNS INT(5)22 BEGIN

23 DECLARE i INT DEFAULT 0;24 SET i = FLOOR(100+RAND()*10);25 RETURNi;26 END$27 DELIMITER;

三、编写存储过程,模拟500W的员工数据

1 /*创建存储过程:往emp表中插入数据*/

2 DELIMITER $3 drop PROCEDURE if EXISTSinsert_emp;4 CREATE PROCEDURE insert_emp(IN START INT(10),IN max_num INT(10))5 BEGIN

6 DECLARE i INT DEFAULT 0;7 /*set autocommit =0 把autocommit设置成0,把默认提交关闭*/

8 SET autocommit = 0;9 REPEAT10 SET i = i + 1;11 INSERT INTO emp(empno,empname,job,mgr,hiredate,sal,comn,depno) VALUES ((START+i),rand_string(6),'SALEMAN',0001,now(),2000,400,rand_num());12 UNTIL i =max_num13 ENDREPEAT;14 COMMIT;15 END$16 DELIMITER;17 /*插入500W条数据*/

18 call insert_emp(0,5000000);

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

1 /*创建存储过程:往dep表中插入数据*/

2 DELIMITER $3 drop PROCEDURE if EXISTSinsert_dept;4 CREATE PROCEDURE insert_dept(IN START INT(10),IN max_num INT(10))5 BEGIN

6 DECLARE i INT DEFAULT 0;7 SET autocommit = 0;8 REPEAT9 SET i = i+1;10 INSERT INTO dep( depno,depname,memo) VALUES((START+i),rand_string(10),rand_string(8));11 UNTIL i =max_num12 ENDREPEAT;13 COMMIT;14 END$15 DELIMITER;16 /*插入120条数据*/

17 call insert_dept(1,120);

五、创建关键字段的索引,这边是跑完数据以后再建索引,会致使建索引耗时长,可是跑数据就会快一些。

1 /*创建关键字段的索引:排序、条件*/

2 CREATE INDEX idx_emp_id ONemp(id);3 CREATE INDEX idx_emp_depno ONemp(depno);4 CREATE INDEX idx_dep_depno ON dep(depno);

测试

测试数据

1 /*偏移量为100,取25*/

2 SELECTa.empno,a.empname,a.job,a.sal,b.depno,b.depname3 from emp a left join dep b on a.depno = b.depno order by a.id desc limit 100,25;4 /*偏移量为4800000,取25*/

5 SELECTa.empno,a.empname,a.job,a.sal,b.depno,b.depname6 from emp a left join dep b on a.depno = b.depno order by a.id desc limit 4800000,25;

执行结果

1 [SQL]

2 SELECTa.empno,a.empname,a.job,a.sal,b.depno,b.depname3 from emp a left join dep b on a.depno = b.depno order by a.id desc limit 100,25;4 受影响的行: 0

5 时间: 0.001s6 [SQL]

7 SELECTa.empno,a.empname,a.job,a.sal,b.depno,b.depname8 from emp a left join dep b on a.depno = b.depno order by a.id desc limit 4800000,25;9 受影响的行: 0

10 时间: 12.275s

由于扫描的数据多,因此这个明显不是一个量级上的耗时。

解决方案

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

由于咱们有主键id,而且在上面建了索引,因此能够先在索引树中找到开始位置的 id值,再根据找到的id值查询行数据。

1 /*子查询获取偏移100条的位置的id,在这个位置上日后取25*/

2 SELECTa.empno,a.empname,a.job,a.sal,b.depno,b.depname3 from emp a left join dep b on a.depno =b.depno4 where a.id >= (select id from emp order by id limit 100,1)5 order by a.id limit 25;6

7 /*子查询获取偏移4800000条的位置的id,在这个位置上日后取25*/

8 SELECTa.empno,a.empname,a.job,a.sal,b.depno,b.depname9 from emp a left join dep b on a.depno =b.depno10 where a.id >= (select id from emp order by id limit 4800000,1)11 order by a.id limit 25;

执行结果

执行效率相比以前有大幅的提高:

1 [SQL]

2 SELECTa.empno,a.empname,a.job,a.sal,b.depno,b.depname3 from emp a left join dep b on a.depno =b.depno4 where a.id >= (select id from emp order by id limit 100,1)5 order by a.id limit 25;6 受影响的行: 0

7 时间: 0.106s8

9 [SQL]

10 SELECTa.empno,a.empname,a.job,a.sal,b.depno,b.depname11 from emp a left join dep b on a.depno =b.depno12 where a.id >= (select id from emp order by id limit 4800000,1)13 order by a.id limit 25;14 受影响的行: 0

15 时间: 1.541s

二、起始位置重定义

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

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

2 SELECTa.id,a.empno,a.empname,a.job,a.sal,b.depno,b.depname3 from emp a left join dep b on a.depno =b.depno4 where a.id > 100 order by a.id limit 25;5

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

7 SELECTa.id,a.empno,a.empname,a.job,a.sal,b.depno,b.depname8 from emp a left join dep b on a.depno =b.depno9 wherea.id > 4800000

10 order by a.id limit 25;

执行结果

1 [SQL]

2 SELECTa.id,a.empno,a.empname,a.job,a.sal,b.depno,b.depname3 from emp a left join dep b on a.depno =b.depno4 where a.id > 100 order by a.id limit 25;5 受影响的行: 0

6 时间: 0.001s7

8 [SQL]

9 SELECTa.id,a.empno,a.empname,a.job,a.sal,b.depno,b.depname10 from emp a left join dep b on a.depno =b.depno11 where a.id > 4800000

12 order by a.id limit 25;13 受影响的行: 0

14 时间: 0.000s

这个效率是最好的,不管怎么分页,耗时基本都是一致的,由于他执行完条件以后,都只扫描了25条数据。

可是有个问题,只适合一页一页的分页,这样才能记住前一个分页的最后Id。若是用户跳着分页就有问题了,好比刚刚刷完第25页,立刻跳到35页,数据就会不对。

这种的适合场景是相似百度搜索或者腾讯新闻那种滚轮往下拉,不断拉取不断加载的状况。这种延迟加载会保证数据不会跳跃着获取。

三、降级策略

看了网上一个阿里的dba同窗分享的方案:配置limit的偏移量和获取数一个最大值,超过这个最大值,就返回空数据。

由于他以为超过这个值你已经不是在分页了,而是在刷数据了,若是确认要找数据,应该输入合适条件来缩小范围,而不是一页一页分页。

这个跟我同事的想法大体同样:request的时候 若是offset大于某个数值就先返回一个4xx的错误。

小结:

当晚咱们应用上述第三个方案,对offset作一下限流,超过某个值,就返回空值。次日使用第一种和第二种配合使用的方案对程序和数据库脚本进一步作了优化。

合理来讲作任何功能都应该考虑极端状况,设计容量都应该涵盖极端边界测试。

另外,该有的限流、降级也应该考虑进去。好比工具多线程调用,在短期频率内8000次调用,可使用计数服务判断并反馈用户调用过于频繁,直接给予断掉。

哎,大意了啊,搞了半夜,QA同窗不讲武德。不过这是很美好的经历了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/556458.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java8中的map与flatmap区别

map:只能返回一个值 flatmap:返回多个值 new ArrayList().stream().map(x -> x);//返回一个 new ArrayList().stream().flatMap(x -> Arrays.asList(x.split(" ")).stream());//返回一个流,也就是多个值 看API声明可以发现,flatmap接受的参数是流…

shell 文件路径有空格_Python学习第57课-shell入门之基本简单命令(一)

【每天几分钟,从零入门python编程的世界!】我们现在学习shell操作,对于shell的命令,我们就把它看做新的语言,shell语言,它是不同于其他编程语言的。就像我们学习一门编程语言,都是从打出“hell …

比较Spring AOP和AspectJ

1. 介绍 当前有多个可用的AOP库,这些库必须能够回答许多问题: 它与我现有的或新的应用程序兼容吗?在哪里可以实施AOP?它与我的应用程序集成的速度有多快?性能开销是多少? 在本文中,我们将着眼…

hough变换直线检测_python+opencv实现霍夫变换检测直线

作者:Ruff_XY功能:创建一个滑动条来控制检测直线的长度阈值,即大于该阈值的检测出来,小于该阈值的忽略 注意:这里用的函数是HoughLinesP而不是HoughLines,因为HoughLinesP直接给出了直线的断点,…

php文件防删改,PHP实现增删改查以及防SQL注入

最近项目调研时,需要在集成板子上做个配置的网页,板子上装的是linux系统,配置信息在一个SQLite数据库中,经过讨论大家决定用PHP做这个网页。由于项目组没一个会PHP的,所以安排我调研下写个Demo,经过几天的研…

c# python 相互调用_【GhPython】Python如何使用“委托”和lambda表达式

【版权声明】| 作者:月之眼| 首发于大水牛参数化设计平台| 如需转载请联系作者| 如果觉得文章不错,欢迎分享 函数作为参数传入 在python中函数是能作为参数输入函数的。这个有点类似于C#中的委托,将一个函数封装到一个委托对象里,…

chimerge算法matlab实现,有监督的卡方分箱算法

实现代码import numpy as npimport pandas as pdfrom collections import Counterdef chimerge(data, attr, label, max_intervals):distinct_vals sorted(set(data[attr])) # Sort the distinct valueslabels sorted(set(data[label])) # Get all possible labelsempty_coun…

金士顿u盘真假软件_简洁轻巧 金士顿DT80 Type-C高速闪存盘评测

从都市的高端会议到普通的日常娱乐,USB高速闪存应用于我们生产生活的方方面面。它小巧便携,稳定可靠的特点吸引了无数人去使用,同时为我们提供了诸多便利。闪存盘也就是日常生活中经常提到的U盘。大多数人对于U盘的印象是老式的USB Micro接口…

php阴影效果,如何使用css3实现文字的单阴影效果和多重阴影效果(

使用css3实现文本阴影效果的原理实现阴影效果主要是用text-shadow属性,根据W3C标准,如果我们想要在IE下兼容CSS3的阴影属性可以使用ie.css3-htc,不过按照标准InternetExplorer9以及更早版本的浏览器暂时不支持text-shadow属性。最基本的语法为…

promise链式调用_这一次,彻底弄懂 Promise

Promise 必须为以下三种状态之一:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。一旦Promise 被 resolve 或 reject,不能再迁移至其他任何状态(即状态 immutable)。基本过程:初始化 Promise 状态(pending)执行 then(..) 注册回调处…

visual studio 判断dropdownlist选的是什么_心理测试:五个小蓝人,你选哪个?测你是不是一个容易追求的人...

下面这张图片里,有五个小蓝人,你觉得自己会是里面的哪一个?A. 站在家里的窗户边B. 站在河边C. 坐在屋顶D. 站在树上E. 骑着鸟飞在空中测试结果选A的你容易追求指数20%。你是一个温柔细腻的人。在别人的眼里,你是一个很贴心的人。在…

java中为何输出框会无限输出,MyBatis启动时控制台无限输出日志的原因及解决办法...

你是否遇到过下面的情况,控制台无限的输出下面的日志:Logging initialized using ‘class org.apache.ibatis.logging.log4j.Log4jImpl adapter.Logging initialized using ‘class org.apache.ibatis.logging.log4j.Log4jImpl adapter.Logging initiali…

基于注解SpringAOP,AfterReturning,Before,Around__springboot工程 @Around 简单的使用__SpringBoot:AOP 自定义注解实现日志管理

基于注解SpringAOP,AfterReturning,Before,Around AOP(Aspect Oriented Programming),即面向切面编程(也叫面向方面编程,面向方法编程)。其主要作用是,在不修…

流浪地球开机动画包zip_【文娱热点】流浪地球2定档2023大年初一;迪士尼计划裁员32000人...

剧集、综艺任嘉伦、白鹿《长安如故》开机11月26日,根据小说《一生一世美人骨》古代篇改编的剧集《长安如故》开机,两位主演任嘉伦和白鹿继现代篇《一生一世》之后再演古代篇,俩人穿着棉服、梳着古装发髻现身开机仪式,心情非常好。…

matlab读气象数据,中国气象数据网

“中国气象科学数据共享服务网”的气象卫星资料与国内其他气象卫星资料发布平台的最大不同之处,在于卫星数据资源内容不同且时间序列相当完整。而且,(1)数据获取更便捷。在线获取数据无需等待邮件通知,无数据下载量限制。共享卫星资源是公益性…

spring中自定义注解(annotation)与AOP中获取注解___使用aspectj的@Around注解实现用户操作和操作结果日志

spring中自定义注解(annotation)与AOP中获取注解 一、自定义注解(annotation) 自定义注解的作用:在反射中获取注解,以取得注解修饰的类、方法或属性的相关解释。 package me.lichunlong.spring.annotation;import java.lang.annotation.Documented; …

python 编译器pyc_有没有办法知道哪个Python版本.pyc文件被编译?

Is there any way to know by which Python version the .pyc file was compiled? 解决方案 You can get the magic number of your Python as follows: $ python -V Python 2.6.2 # python >>> import imp >>> imp.get_magic().encode(hex) d1f20d0a To ge…

Spring AOP——Spring 中面向切面编程

前面两篇文章记录了 Spring IOC 的相关知识,本文记录 Spring 中的另一特性 AOP 相关知识。 部分参考资料: 《Spring实战(第4版)》 《轻量级 JavaEE 企业应用实战(第四版)》 Spring 官方文档 W3CSchool Spri…

python getchar,Linux C编程学习:getchar()和getch()

getchar函数名: getchar功 能: 从stdin流中读字符用 法: int getchar(void);注解:getchar有一个int型的返回值,当程序调用getchar时程序就等着用户按键,用户输入的字符被存放在键盘缓冲区中直到用户按回车为止(回车字符也放在缓冲区中)。当用…

python 折线图中文乱码_彻底解决 Python画图中文乱码问题--Pyplotz组件

1 源起 自从开始学习Python,就非常喜欢用来画图。一直没有需求画要中文显示信息的图,所以没有配置Python中文的环境。由于昨天就需要画几十个形式相同,只是数据不同的图,并且需要显示中文信息。如果用Excel画图会很浪费时间&#…