深挖MYSQL大表加索引

news2024/10/7 7:26:53

在这里插入图片描述

深挖MYSQL大表加索引

起因是这样的,有一张表存在慢sql,查询耗时最多达到12s,定位问题后发现是由于全表扫描导致,需要对字段增加索引,但是表的数据量600多万有些大,网上很多都说对大表增加索引可能会导致锁表,查阅了一些资料,可以说网上说了很多,但是都很笼统,听别人说不如自己去验证,于是开启了验证之旅

首先新建一张表test_page1

CREATE TABLE `test_page1`  (
  `id` int(11)  NULL,
  `username` int(252) not  NULL,
  `password` int(252)  NULL,
  `create_time` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci not NULL ,
  `update_time` datetime(0) NULL DEFAULT NULL,
  PRIMARY KEY (`create_time`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1000001 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

第二步,像表中干他个600w条数据 这一步网上有很多教程,有通过sql直接在mysql客户端插入数据,还有通过代码插入数据的,最初为了方便,我是想再mysql客户端直接通过存储过程插入数据,但是插入速度十分感人

在这里插入图片描述
果断放弃,毕竟600w条,不想等到猴年马月,于是就选择用代码的方式插入,其实就是多费了一些力气而已,上代码,开整
在这里插入图片描述

public class Connect {
    //    导入驱动jar包或添加Maven依赖(这里使用的是Maven,Maven依赖代码附在文末)
    static {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    //  获取数据库连接对象
    public static Connection getConn() {
        Connection conn = null;
        try {
            //  rewriteBatchedStatements=true,一次插入多条数据,只插入一次
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/xxx?rewriteBatchedStatements=true", "root", "xxx");
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return conn;
    }

    //  释放资源
    public static void closeAll(AutoCloseable... autoCloseables) {
        for (AutoCloseable autoCloseable : autoCloseables) {
            if (autoCloseable != null) {
                try {
                    autoCloseable.close();
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }


}
public class InsertData {
    private static ThreadPoolExecutor getDefaultThreadPool() {
        ThreadPoolExecutor result = new ThreadPoolExecutor(0, 1000, 1, TimeUnit.SECONDS, new SynchronousQueue<>());
        result.setThreadFactory(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "deterministic runner thread");
            }
        });
        return result;
    }
    /*  因为数据库的处理速度是非常惊人的 单次吞吐量很大 执行效率极高
    addBatch()把若干sql语句装载到一起,然后一次送到数据库执行,执行需要很短的时间
    而preparedStatement.executeUpdate() 是一条一条发往数据库执行的 时间都消耗在数据库连接的传输上面*/
    public static void main(String[] args) {
            for (int j = 0; j < 100; j++) {
                long start = System.currentTimeMillis();    //  获取系统当前时间,方法开始执行前记录
                Connection conn = Connect.getConn();        //  调用刚刚写好的用于获取连接数据库对象的静态工具类
                String sql = "insert into test_page1 values(null,?,?,?,NOW())";  //  要执行的sql语句
                PreparedStatement ps = null;
                getDefaultThreadPool().execute(() -> {
                    try {
                    PreparedStatement finalPs = conn.prepareStatement(sql);

                        //  不断产生sql
                        for (int i = 0; i < 20000; i++) {
                            finalPs.setString(1, Math.ceil(Math.random() * 1000000) + "");
                            finalPs.setString(2, Math.ceil(Math.random() * 1000000) + "");
                            finalPs.setString(3, UUID.randomUUID().toString());  //  UUID该类用于随机生成一串不会重复的字符串
                            finalPs.addBatch();  //  将一组参数添加到此 PreparedStatement 对象的批处理命令中。
                        }
                        int[] ints = new int[0];//   将一批命令提交给数据库来执行,如果全部命令执行成功,则返回更新计数组成的数组。

                        ints = finalPs.executeBatch();
                        //  如果数组长度不为0,则说明sql语句成功执行,即数据添加成功!
                        if (ints.length > 0) {
                            System.out.println("数据添加成功!!");
                        }
                    } catch (SQLException e) {
                        throw new RuntimeException(e);
                    }finally {
                        Connect.closeAll(conn, ps);  //  调用刚刚写好的静态工具类释放资源
                    }
                  });
                long end = System.currentTimeMillis();  //  再次获取系统时间
                System.out.println("所用时长:" + (end - start) / 1000 + "秒");  //  两个时间相减即为方法执行所用时长
            }
    }
}

代码之所以快,很大的原因是由与代码开启了多线程,异步插入,但在实际执行过程中,也会出现问题,比如把插入的数据量搞太大导致了OOM,这个可以修改本地的JVM,另一种就是同时插入太多,数据库连接不够了,导致报错,但这都不是重点,因为我们的重点是大表加索引。 代码执行后20分钟内,插入了600w条数据。

这时候就开始我们的验证表演了。

首先,说一下网上描述的大表加索引会出现的问题

  1. 如果在执行事务的时候,如果存在目标表的慢sql,这时对目标表增加索引,会导致目标表被锁,进入Waiting for table metadata lock状态,进入Waiting for table metadata lock状态后不能读也不能写
  2. 加索引属于DDL操作,DDL操作执行的时候,会对表加锁

然后开始我的尝试 先对表加个索引,用时15.19s

alter table test_page1 add index create_time_index(create_time)

在这里插入图片描述
然后我们开启事务,并对该表执行个慢查询,并对表新建一个索引

BEGIN;

select * from test_page1 where username = 852;
  alter table test_page1 add index create_time_index(create_time)

这个慢查询有8s,足够出现问题了,很有信心
在这里插入图片描述
然而,并没有出现期望的结果,凉凉,难道网上说的都是假的,本身不存在这种情况,苦思之下,似乎找到问题 我是通过dbveaer来执行的sql,同事执行两个sql是在两个tab页上执行,会不会是虽然在dbveaer的两个tab页同时执行,但是dbveaer还是一个一个排队执行的sql呢?我想大概率是这样

我又通过dbeaver新建一个数据库连接,让开启事务,并对该表执行个慢查询和对表增加索引在两个连接执行,这时执行show processlist命令,终于复现了
在这里插入图片描述
加索引命令的进程进入了Waiting for table metadata lock状态 网上说Waiting for table metadata lock状态后不能读也不能写,是不是这样呢?,来执行下查询,畅通无阻,所以说网上是错误的,是可以读的,那能不能写呢,我们执行下sql

insert into test_page1(id,username,password,create_time,update_time) values(null,1,2,'6144423733',NOW());

报错了,死锁了Deadlock found when trying to get lock; try restarting transaction,这就验证了无法进行写操作
在这里插入图片描述
那Waiting for table metadata lock状态会持续到什么时候呢,在验证过程中,发现了两种方式 第一种,事务提交后,锁状态取消 第二种,这种比较神奇,就是刚刚操作过的,对表进行插入操作,这个时候会报错,但是报错后,mysql会自动杀掉事务进程并解锁(这真的很神奇),但是事实就是这样,很糟心。

还有另外一个点要验证就是加索引属于DDL操作,DDL操作执行的时候,会对表加锁,之前我理解错了,以为加锁是表锁,会锁表的数据,但是执行ddl操作时是不会组织数据的写入的,但是另一个连接去执行DDL操作会进入等待状态,这就是多,DDL操作的确会加锁,但是他锁的不是数据而是表结构。

经过一番蛮长的论证,终于验证了什么情况下加索引会锁表,为什么有时候加索引时间会很长,加字段时间会很长,所以,大家加索引最好选择选择在一个业务低峰期加,另外,要注意优化系统,减少系统中慢sql的出现,这样会降低锁表的可能性。

另外如果表被锁住,处于Waiting for table metadata lock状态,这时候我们也可以通过杀掉线程id的方式来解锁,执行show processlist命令,找到线程id,执行kill +id,也能完成解锁。

后记

通过自己实际验证,发现网上说的大部分是正确的,但是没有那么细致,比如解锁的条件是什么,怎么解锁,锁表是锁表结果还是锁数据,实际验证之后得到了很多收获,所以技术还是要深挖

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

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

相关文章

垂直行业(新站)SEO流量快速起飞的核心思路

现在做站不比以前了&#xff0c;不管你是做百度也好&#xff0c;还是谷歌也罢&#xff0c;对于行业精准SEO流量来说肯定是没有以前那么容易做了。但是不容易做不代表没有机会做&#xff0c;机会一直还是有的&#xff0c;尤其是最近百度打击泛站&#xff0c;对于垂直行业来说其实…

chatgpt赋能python:Python图片尺寸大小修改指南

Python图片尺寸大小修改指南 在现代网站设计中&#xff0c;图像是非常重要的一部分。图片质量和大小是网站排名和用户体验的关键因素。一般来说&#xff0c;网站应该尽量避免使用过多的大图片&#xff0c;因为它们会使用户等待过长的时间&#xff0c;同时也会降低网站的加载速…

JAVA基础练习(6)

目录 1.冒泡排列学员成绩(降序) 2.常用Arrays类的应用 2.1.sort 2.2.equals 2.3.toString 2.4.fill 2.5.Arrays.copyOf 2.6.binarySearch 3.利用二维数组计算成绩 1.冒泡排列学员成绩(降序) package ch06;import java.util.Arrays; import java.util…

代码随想录算法训练营第四十一天|343. 整数拆分|96.不同的二叉搜索树

LeetCode343. 整数拆分 动态规划五部曲&#xff1a; 1&#xff0c;确定dp数组&#xff08;dp table&#xff09;以及下标的含义&#xff1a;dp[i]&#xff1a;分拆数字i&#xff0c;可以得到的最大乘积为dp[i]。 2&#xff0c;确定递推公式&#xff1a;可以想 dp[i]最大乘积…

下载安装微软office的详细步骤

目录 一、前言 二、下载路径 &#xff08;一&#xff09;wps office 办公软件下载地址 1.wps office办公软件下载地址 &#xff08;二&#xff09;微软office 办公软件下载地址--2021 1.专业增强版 2.专业版 3.家庭专业版 4.家庭企业版 &#xff08;三&#xff09;…

JAVA基础练习(1)

目录 1.练习一:使用变量存储数据&#xff0c;实现个人简历信息的输出 2.练习二:使用Scanner类获取键盘输入的会员卡号&#xff0c;并将该数据存储在变量中&#xff0c;输出这个变量的信息 3.练习三:键盘输入四位数字的会员卡号,使用“/”和“%”运算符分解获得会员卡各个位上…

(二)模拟实现 《资源发现》框架

文章目录 前言资源发现《资源发现》概述技术难点 《资源发现》基本思想《资源发现》框架思考需求分析技术选择 《资源发现》技术难点实现《资源发现》框架实现资源发现基础类实现资源注册中心的实现资源持有者和资源请求者资源持有者和资源请求者功能具体实现 前言 《资源发现…

JAVA基础练习(4)

目录 1.利用循环打印九九乘法表 2.使用循环输出 100、95、90、85.......5 3.输入星期查看对应结果 4.几行数字展示 5.打印1-100之间13的倍数&#xff0c;使用for循环 6.用*来打印&#xff0c;根据用户输入rows和columns&#xff0c;来打印响应矩形 7.输入三个班&#xff…

YOLO8自定义检测实战

文章目录 资料模型介绍(或者叫weights)安装安装ultralytics&#xff08;yolo&#xff09;Torch测试命令 CLI命令行通过COCO128数据集体验yolov8标签predictsegment下载COCO 2017数据集ValTrain 自定义数据集标注标注软件labelimg分析训练结果 获得最佳训练结果提示 资料 Docs:…

docker学习记录

1.什么是docker&#xff1f; Docker是一个容器引擎&#xff0c;使用 Linux 内核功能&#xff08;如命名空间和控制组&#xff09;在操作系统之上创建容器。除了作为一种容器技术之外&#xff0c;Docker 还具有定义明确的包装器组件&#xff0c;这使打包应用程序变得十分容易&am…

Windows认证机制

windows认证基础 windows的认证包括三个部分&#xff1a; 本地认证&#xff1a;用户直接操作计算机登录账户网络认证&#xff1a;远程连接到工作组中的某个设备域认证&#xff1a;登录到域环境中的某个设备 本地认证 1、用户输入密码 2、系统收到密码后将用户输入的密码计…

LeetCode 周赛 348(2023/06/05)数位 DP 模板学会了吗

本文已收录到 AndroidFamily&#xff0c;技术和职场问题&#xff0c;请关注公众号 [彭旭锐] 加入知识星球提问&#xff01; 往期回顾&#xff1a;LeetCode 单周赛第 347 场 二维空间上的 LIS 最长递增子序列问题 周赛 348 概览 T1. 最小化字符串长度&#xff08;Medium&…

chatgpt赋能python:Python基础教程:如何利用Python进行地区查询

Python基础教程&#xff1a;如何利用Python进行地区查询 在现代社会&#xff0c;人们越来越关注自己所处的地理位置和周边环境。这就导致了地区查询变得越来越流行&#xff0c;因为它可以让人们更加方便地获取自己想要的信息。 Python作为一门强大的编程语言&#xff0c;不仅…

chatgpt赋能python:Python在线模拟:提高编程技能的必备工具

Python在线模拟&#xff1a;提高编程技能的必备工具 Python是一种广泛应用于各行业的编程语言&#xff0c;也是许多工程师的选择。成为一名Python工程师意味着拥有高薪、稳定的职业和无尽的机会。但是如何成为一名高效的Python工程师&#xff1f;在线模拟器做到了提高技能和编…

Servlet与Mabatis-1

Web 应用开发 get 和 post 请求方法 &#xff08;重点&#xff09; http 协议中定义的请求方法有 DELETE、HEAD、GET、OPTIONS、POST、PUT、TRACE 在 http 协议中的两种常见的传参方法 get/post&#xff0c;例如 get 和 post 的共同点&#xff1a;Get 提交和 post 提交都是…

SpringBoot 3.x 新特性

SpringBoot 3.x 新特性 引用文章地址 SpringNative GraalVM 打包 SpringBoot 为 Linux 的 单文件应用程序 目录 JDK版本spring.factories废弃GraalVM — Spring Native三方包升级jakarta代替javax改进ConstructorBinding检测Log4j2增强杂七杂八 JDK版本 Springboot 3.x 基…

chatgpt赋能python:Python图片处理:让图像处理更简单

Python 图片处理&#xff1a;让图像处理更简单 作为一门强大的编程语言&#xff0c;Python 可以处理多种任务&#xff0c;其中之一是图形处理。Python 程序员可以使用各种库和工具&#xff0c;在不同的平台上进行图片处理、编辑和转换。在本文中&#xff0c;我们将讨论 Python…

chatgpt赋能python:Python图像分块的简介

Python 图像分块的简介 Python 是一种高级编程语言&#xff0c;越来越多地应用于图像处理领域。图像分块是一种常见的图像处理技术&#xff0c;它是将图像分成大小相等的小块&#xff0c;从而方便进行后续的处理或者分析。 在这篇文章中&#xff0c;我们将着重介绍 Python 图…

MySQL UNION使用介绍及示例

MySQL UNION使用介绍及示例 1 用法介绍2 使用示例2.1 数据准备2.2 查询示例 1 用法介绍 说明作用UNION运算符用于组合两个或更多SELECT语句的结果集使用前提UNION中的每个SELECT语句必须具有相同的列数 1. 这些列的数据类型必须兼容&#xff1a;类型不必完全相同&#xff0c;…

(八)Spring之IOC控制反转、DI依赖注入介绍和使用(详解)

文章目录 前言SpringSpring IOC 简介BeanIOC 概述IOC 本质理解 Spring IOC 应用IOC xml装配IOC 依赖注入IOC Bean的作用域 IoC 自动装配Bean 的自动装配注解实现自动装配 IoC 使用注解开发模拟实现Spring IoC 前言 “Spring”在不同的上下文中表示不同的事物。它可以用来引用 …