MySQL1000万条数据分页查询优化_超过1000w条数据分页_一颗向上的草莓的博客-程序员宅基地

技术标签: 数据库—Mysql  mysql查询优化  

这种问题的解决就是通过构建一个新的小表,以小表来join驱动大表;或者构建一个子查询语句,用exist,in做查询;或者注意索引下推和索引覆盖的使用。

概述


今天一个朋友问我一个问题,说MySQL很多数据查询怎么优化。我就直接说了个分页查询就行了啊,查询字段加索引。然而结果并没有我想象得那么简单,他分页查询直接把服务查崩了。原来他们数据量已经有好几百万了。你可能会问不就几百万数据吗,分页只查询10条怎么可能查询会很慢。后面我会模拟1000万条数据分页查询。

准备工作

1.新建一个测试库,我这里就叫test

在这里插入图片描述

2.在此库中新建一张student表

CREATE TABLE `student` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `name` varchar(10) NOT NULL COMMENT '姓名',
 `age`  int(10) unsigned NOT NULL COMMENT '岁数',
 PRIMARY KEY (`id`),
 KEY `age` (`age`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


3.此时需要新建1000万条数据,这里我提供一段简短得代码。复制过去就可以用

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.PreparedStatement;

import java.sql.SQLException;

public class InsertTest {

public static void main(String[] args) throws ClassNotFoundException, SQLException {
    final String url = "jdbc:mysql://127.0.0.1/test?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC";
    final String name = "com.mysql.cj.jdbc.Driver";
    final String user = "root";
    final String password = "928888";
    Connection conn = null;
    //指定连接类型
    Class.forName(name);
    //获取连接
    conn = DriverManager.getConnection(url, user, password);
    if (conn != null) {
        System.out.println("获取连接成功");
        //插入操作
        batchInsert(conn);
    } else {
        System.out.println("获取连接失败");
    }

}
public static void batchInsert(Connection conn) {
    // 开始时间
    Long begin = System.currentTimeMillis();
    // sql前缀
    String sqlPrefix = "INSERT INTO student (name, age) VALUES ";
    try {
        // 保存sql后缀
        StringBuffer sqlSuffix = new StringBuffer();
        // 设置事务为非自动提交
        conn.setAutoCommit(false);
        //准备执行语句
        PreparedStatement pst = (PreparedStatement) conn.prepareStatement(" ");
        // 外层循环,总提交事务次数
        for (int i = 1; i <= 100; i++) {
            sqlSuffix = new StringBuffer();
            // 第j次提交步长
            for (int j = 1; j <= 100000; j++) {
                // 构建SQL后缀
                sqlSuffix.append("('" + "cxx" + j + "'," + 1 + "),");
            }
            // 构建完整SQL
            String sql = sqlPrefix + sqlSuffix.substring(0, sqlSuffix.length() - 1);
            // 添加执行SQL
            pst.addBatch(sql);
            // 执行操作
            pst.executeBatch();
            // 提交事务
            conn.commit();
            // 清空上一次添加的数据
            sqlSuffix = new StringBuffer();
        }
        // 头等连接
        pst.close();
        conn.close();
    } catch (SQLException e) {
        e.printStackTrace();
    }
    // 结束时间
    Long end = System.currentTimeMillis();
    // 耗时
    System.out.println("1000万条数据插入花费时间 : " + (end - begin)  + "ms");
    System.out.println("插入完成");
}
}

此处可能执行时间比较长,各位小伙伴不要心急,下图是我得执行时间

在这里插入图片描述


分页查询测试

1.首先我测试一条我们常见的分页sql,大家可以猜测下执行时间

select * from student where age = 1 limit 9000000,1    

在这里插入图片描述

2.这里我第一次执行了100多秒,第二次因为有缓存所以只执行了40多秒
我第一次测试我也不敢相信自己得眼睛,what,不就查询一条数据怎么可能需要这么久,而且我的age还是建立了索引得,照理说不应该这么慢啊(为此我还专门查询了sql执行计划,确实是走了索引得,如下图所示)
执行器脚本如下

EXPLAIN select * from student where age = 1 limit 9000000,1

在这里插入图片描述

3.可能有的人不知道limit 9000000,1是什么意思,我大概解释一下就是从第9000000万行开始查询一行

4.后面我查了相关资料才知道原来起始数据开始比较小的时候不会影响查询,但是起始数据后数据增大后查询就会变得特别慢(起始数据得意思是从多少行开始)

原因分析


1.因为数据表是InnoDB,根据InnoDB索引的结构,查询过程为:

通过普通索引查到主键值(找出所有age=1的id)
再根据查到的主键值通过主键索引找到相应的数据块(根据id找出对应的数据块内容)
根据offset的值,查询9000001次主键索引的数据,最后将之前的9000000条丢弃,取出最后1条(这里应该是导致数据查询缓慢得原因)。
2.下面我用具体的实例来分析

首先你执行查询所有列得脚本

select * from student where age = 1 limit 9000000,1


此处可能需要等几十秒
执行完后执行下面者条语句来查看buffer pool的内容
 

select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in('primary','age') and TABLE_NAME like '%student%' group by index_name;

在这里插入图片描述

此时buffer pool中关于student表有186个数据页,64个索引页(此处不一定准确,因为我第一次执行比这个多很多,这里是有缓存。如果你们想测试最初得值可以关闭MySQL重新启动)

我们在执行只查询id列得脚本
 

select id from student where age = 1 limit 9000000,1

在这里插入图片描述

此时可以看出数据页减少了很多,这里我们可以得出结论:MySQL查询时,起始数量过大影响性能的原因是多次通过主键索引访问数据块的I/O操作。
解决方案
1.根据上面的分析,我们知道查询所有字段会导致主键索引多次访问数据块造成的I/O操作。

2.因此我们先查出偏移后的主键,再根据主键索引查询数据块的所有内容即可优化。
3.优化后得脚本:

SELECT
    a.*
FROM
    student a
RIGHT JOIN (
    SELECT
        id
    FROM
        student
    WHERE
        age = 1
    LIMIT 9000000,
    1
) t ON a.id = t.id

在这里插入图片描述

此时我们可以看出执行时间较少了很多

总结
以上就是全篇文章得内容,感谢各位百忙之中抽出时间来查看本文。希望本文能对大家在工作中有一定的帮助。希望大家也能指出相应得问题,大家共同学习。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u014532775/article/details/107035489

智能推荐

cocos creator主程入门教程(十一)—— 有限状态机和行为树_cocoscreator状态机_houjia159的博客-程序员宅基地

本篇介绍有限状态机和行为树。有限状态机用于有限的状态下的AI,由于同时只能处于一个状态,多个状态需要多个有限状态机,一般用于简单的AI行为。行为树是基于固定行为,通过遍历树来决定采用哪种行为。行为的设计和执行采用解释器模式,由策划设计数据,程序解析执行,行为组合的灵活性高,比较适合剧情NPC。但当树比较深、分支比较多时,遍历的效率就需要考虑优化。一般我们认为有限状态机执行的性能优于行为树,但不能胜任复杂、灵活的AI设计。而行为树则比较适合复杂、灵活的AI设计。先介绍下有限状态机。考虑在一个类似《刀._cocoscreator状态机

Golang和jwt_TwistedFater的博客-程序员宅基地

需要使用第三方库go get -u github.com/dgrijalva/jwt-go// jwt.SigningMethodRS256 定义我们的jwt为sha256加密,也就是jwt的头部// jwt.MapClaims{} 这个就是我们jwt中的payload,也就是你需要验证的数据// 这里说下,这个包里已经有了一些写好的func GenerateToken(user...

【AIChallenger 2018 】【眼底水肿病变区域分割】冠军解决方案_https://challenger.ai/dataset/fld2018 眼底水肿 数据下载_科研民工plus的博客-程序员宅基地

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。 ..._https://challenger.ai/dataset/fld2018 眼底水肿 数据下载

WPF Image显示图片变大问题?_wpf image 大小-程序员宅基地

最近使用Image显示图片,我想让Image显示图片为图片本身大小,因此设置Stretch属性为None,但是显示出来的图片比原是图片大一些,而且比较模糊。最后找到原因,这和WPF默认渲染以96dpi渲染,而我美工给的图片属性是72dpi所以导致出现这样的问题。解决办法:第一种:手动设置Image的Width和Height和图片一样的大小。第二种:让美工将也导出96dpi的图片。..._wpf image 大小

经史文哲_a412588063的博客-程序员宅基地

1、人不犯我,我不犯人;人若犯我,礼让三分;人再犯我,我还一针;人还犯我,斩草除根。 2、我允许你走进我的世界,但不许你在我的世界里走来走去。 3、明骚易躲,暗贱难防。 4、天没降大任于我,照样苦我心智,劳我筋骨。 5、执子之手,方知子丑,泪流满面,子不走我走。 6、红豆不长南国,长我脸上了,真相思! 7、我深信,会有一个男人

k8s报错:pod状态为pending,coredns的状态是pending的解决办法_coredns pending_yjssjm的博客-程序员宅基地

错误一:coredns的状态是pending原因1:master上的flannel镜像拉取失败,导致获取不到解析的IP解决方法:[root@master ~]# kubectl delete -f kube-flannel.yml #先删除安装的CNI插件[root@master ~]# docker load --input flannel-0.11.0-amd64.tar #导..._coredns pending

随便推点

Sublime Text3使用Package Control安装插件_Kun.ye的博客-程序员宅基地

问题描述:从打开Package Control ,选择Install Package,到时显示插件列表要很久解决办法:一、 点击下载channel_v3.json到本地,并记录下路径,建议放在sublime text3的安装目录下(例如我的:)...

双非之光,厦大这个学院不偏爱985、211学生_厦门大学信息学院是几本-程序员宅基地

1、院校介绍厦门大学信息学院(国家示范性软件学院)于2019年6月由原信息科学与技术学院和原国家示范性软件学院(简称软件学院)合并组建而成。其设5个学系(1个中心)和1个研究院。学系包括人工智能系、计算机科学系、软件工程系(软件工程中心)、信息与通信工程系和网络空间安全系,同时设立人工智能研究院。信息学院同时挂牌“国家示范性软件学院”[1]。厦大信院为国家培养了大批信息人才,具有浓厚的人文气息,在夏令营阶段深受保研er的宠爱~2、培养特色​※以上信息综合搜集整理自院系官网。如有信息偏误,_厦门大学信息学院是几本

处理bug的一些经验总结_brucele1717的博客-程序员宅基地

如何积累解决bug的经验?遇到bug并解决了,详细把bug表现描述出来,并把解决经过写出来,做成笔记,就算以后不翻看,这样至少会加深你对类似bug的印象,下回就会知道类似的问题如何解决;程序执行缓慢,首先应该检查数据结构是否合理,然后检查遍历这个数据结构的遍历语句是否写复杂了,能不能把遍历降低;遇到bug可以与周围的同事或朋友进行探讨,别人的思路可能会给你帮助,

QT:QToolBox抽屉效果_qt中使用qtoolbox实现抽屉效果_csu_zipple的博客-程序员宅基地

一般步骤创建QToolBox的子类建立QGroupBox用Layout布局管理初始化QGroupBox,并且添加布局中的控件用子类的addItem方法添加相应的(QWidget*)QGroupBox,这里强制转换类型关键代码layout中的控件在实际应用场景中可以自由替换…其实最近很想学QCEF,嵌入Web开发…. setWindowTitle("抽屉效果");_qt中使用qtoolbox实现抽屉效果

(转载)http请求连接池 - HttpClient 的 PoolingHttpClientConnectionManager-程序员宅基地

转载,原文地址:https://yq.aliyun.com/articles/1254两个主机建立连接的过程是很复杂的一个过程,涉及到多个数据包的交换,并且也很耗时间。Http连接需要的三次握手开销很大,这一开销对于比较小的http消息来说更大。但是如果我们直接使用已经建立好的http连接,这样花费就比较小,吞吐率更大。传统的HttpURLConnection并不支持连接池,如果要实现连接池的...

推荐文章

热门文章

相关标签