记一次MySQL迁移并从MySQL5.6升级到5.7后查询慢了几十倍的问题

起因

因为生产环境数据量越来越大,客户越来越多,项目功能也越来越多,项目本身也越来越多,导致之前的服务器内存、硬盘都已经渐渐的不够用了,当时出现了2种解决方案,增加服务器配置和新购服务器,但是就算是新增硬盘,也需要对数据库进行迁移,所以就采用了新购服务器的方案,并且因为之前是高效云盘,出现过IO占满的情况,所以对于新购的服务器采用了SSD硬盘,理论上速度会飞起来了,实际上我在新服务器上安装MySQL5.7,因为听说MySQL5.7性能提升了N倍,还支持json(虽然对我们没什么用),但是毕竟MySQL8已经出来了,说明MySQL5.7也肯定稳定好了。so,就找了个夜深人静的晚上偷偷的吧数据库迁移过来了,然后开启慢查询日志,限制为5秒,于是开始各种测试,然后查看了一下慢查询日志,一堆慢查询日志,所以有了这篇文章,为什么会出现这么多慢查询,以及如何解决

开始排查

将慢查询SQL拿出来,发现主要的慢查询SQL都是链表查询的语句,也就是说查询语句本身非常复杂,所以就把SQL语句放回之前的数据库执行,发现之前的数据库都是不到1秒就查询出来了,而在新的数据库上最慢能达到140多秒,这明显就不正常了,毕竟新的MySQL服务器无论是CPU、内存、还是硬盘相对于以前的MySQL服务器来说,都是好了不止一星半点,如果说性能差不多还能接受,但是一下子慢了这么多,就明显是有异常了,于是开始挨个排查

排查第一步:配置问题

首先2边服务器的配置文件是一模一样的,因为就是从旧服务器吧配置文件复制过来的,但是因为MySQL的版本不一致,所以怀疑是因为新版有些配置不一样,所以导致的,于是百度了MySQL5.7的优化配置,同时根据机器的内存CPU等硬件情况调整了部分配置,重启MySQL,执行语句,效果不明显,平均下来能快1秒左右,但是这1秒本身也可以认为是查询波动,所以不是配置的问题

排查第二步:硬件问题

首先CPU和内存应该不会有什么问题,唯一可能性就是SSD硬盘的问题,因为之前看到过因为SSD硬盘导致K,V键值存储性能低下,跟机械硬盘完全不在同一个等级上,所以怀疑SSD是否需要开启什么特别的配置才行,于是百度,发现虽然有针对SSD的优化配置,但是也没有因为用了SSD导致速度非常慢的情况,于是针对SSD进行了优化配置,重启MySQL,执行语句,效果微乎其微

排查第三步:语句问题

也是实在没办法了,才想到这个问题,但是我自己都觉得不大可能,而且语句本身也优化的差不多了,小结果集驱动大结果集,索引根据where条件创建等。毕竟就算MySQL升级也不会说改变SQL语法之类的,最多就是在优化SQL的进行了一些特殊处理,所以先查看一下SQL语句的索引执行情况于是desc sql语句查看,跟旧库上面差别

旧库(MySQL5.6)


新库(MySQL5.7)


在2个库数据,索引相同的情况下,居然会出现索引引用和命中不一样的情况,所以怀疑是否是迁移数据库的时候导致索引数据被破坏,于是百度去找,还真的发现了一个例子,也是迁移数据库后查询非常慢,后面重建索引之后恢复了,于是准备重建索引,由于表非常多,所以写了一个工具类来重建索引(唯一索引和普通索引,不包含主键索引),核心代码如下:

List<HashMap> list = mapper.select1();
HashMap<String,HashMap<String,Object>> temp = new HashMap<>();
for(HashMap map : list){
String tableName = map.get("TABLE_NAME").toString();
String indexName = map.get("INDEX_NAME").toString();
String nonUnique = map.get("NON_UNIQUE").toString();
String columnName = map.get("COLUMN_NAME").toString();
if(temp.containsKey(tableName+"|"+indexName)){
HashMap<String,Object> value = temp.get(tableName+"|"+indexName);
List<String> columns = (List<String>) value.get("columns");
columns.add(columnName);
}else{
HashMap<String,Object> value = new HashMap<>();
value.put("nonUnique",nonUnique);
List<String> columns = new ArrayList<>();
columns.add(columnName);
value.put("columns",columns);
value.put("indexName",indexName);
value.put("tableName",tableName);
temp.put(tableName+"|"+indexName,value);
}
}
List<String> creates = new ArrayList<>();
List<String> drops = new ArrayList<>();
for(Map.Entry<String,HashMap<String,Object>> entry:temp.entrySet()){
String create = null;
String tableName = entry.getValue().get("tableName").toString();
String indexName = entry.getValue().get("indexName").toString();
String nonUnique = entry.getValue().get("nonUnique").toString();
List<String> columns = (List<String>) entry.getValue().get("columns");
drops.add("DROP INDEX "+indexName+" ON "+tableName+";");
if("0".equals(nonUnique)){
//唯一键索引
create = "CREATE UNIQUE INDEX "+indexName+" ON "+tableName+" (";
}else{
//创建普通索引
create = "CREATE INDEX "+indexName+" ON "+tableName+" (";
}
for(int i = 0;i < columns.size();i++){
if(i == columns.size() - 1){
create += columns.get(i)+");";
}else{
create += columns.get(i)+",";
}
}
creates.add(create);
}
for(String str : drops){
System.out.println(str);
}
for(String str : creates){
System.out.println(str);
}
查询所有索引SQL代码如下:

select * from information_schema.statistics WHERE INDEX_SCHEMA=‘xxxx‘ AND INDEX_NAME<>‘PRIMARY‘
其中xxxx是数据库实例名,代码执行完成后将打印出来的SQL语句放进SQL里面执行即可,当然也可以在使用Java调用SQL执行,不过我为了随时观察状况,所以把SQL复制出来执行

重建索引完成后执行SQL语句,发现速度还是没有明显变化,说明不是因为索引数据异常的问题。

检查MySQL5.7新特性

百度查看MySQL5.7有没有更新什么新特性,看到了derived_merge特性,因为derived_merge是MySQL5.7的新的SQL优化方式,所以试着将derived_merge关闭,执行SQL

set GLOBAL optimizer_switch=‘derived_merge=off‘
执行SQL,发现速度比旧服务器还快,然后用desc查看SQL索引使用情况,跟旧服务器也一样,于是问题解决

关闭derived_merge后的新问题

本来以为关闭derived_merge后就万事大吉了,但是服务器的CPU占满却说明事情没有那么简单,top命令查看服务器CPU占满的原因发现是因为MySQL(肯定是MySQL,毕竟服务器就这一个软件),执行命令:

show full processlist;
查看卡住的链接信息,发现有大量的视图查询卡住,于是把SQL语句复制出来,发现只是查询单条数据,理论上不会这样慢,为了找出原因,停止测试,重启MySQL,执行视图SQL语句,发现完全卡住几分钟都不能执行完成,强行停止,检查视图的SQL是否有异常,发现视图的SQL也是普通的SQL(4个表的关联查询),理论上来说不会耗费这么久的时间,把创建视图的SQL语句拿出来跟执行的视图的SQL条件拼接起来,用desc查看,发现索引正常命中,于是试着执行一次SQL,结果非常意外,速度非常快,所以以为是服务器发疯,但是为了测试好,就又执行一下视图的SQL,结果为卡死。也就是说视图本身的SQL执行没有任何问题,但是使用视图查询,就会进入卡死状态。于是使用desc 查看视图SQL索引命中情况,发现结果跟直接的SQL不同,下面是对比图:

视图


视图SQL


视图的索引命中情况明显比视图SQL索引命中多了一个索引,但是为什么会造成卡死呢,原因就在多的那个索引身上,仔细看可以看到,索引命中的行有83141272975行,11位数,上百亿,难怪会卡死,索引命中了上百亿的数据,那跟没有命中索引也没有区别了,而且最为关键的是,我们整个库所有表加起来应该也没有上百亿的数据啊,毕竟目前最大的表数据量也才近千万,所以这个索引肯定是有问题,数据也有问题,但是具体什么问题,就不是我了解的了,毕竟不是专业搞数据库的,所以也希望了解的能帮我解答一下。

知道问题后,感觉解决就简单了,百度搜索了一下MySQL5.7对视图是否进行了优化,但是不管是百度还是谷歌都没有找到合适的答案,毕竟视图本身也只是存储了一个SQL语句而已,并没有保存实际数据,也就是说就算优化也是针对SQL语句本身进行优化,但是SQL语句本身执行又没有任何问题,而且心想MySQL不可能将这么大个bug放出来吧,于是回想之前调过的参数,是否是因为修改了配置导致的,因为之前主要修改特性的配置就derived_merge,所以怀疑是因为derived_merge导致的,于是又打开derived_merge

set GLOBAL optimizer_switch=‘derived_merge=on‘
执行视图,一切正常

排查第四步:索引命中问题

由于关闭了derived_merge会导致视图查询问题,而系统中用到了很多视图,所以如果不用视图的话需要对系统进行大的改动导致关闭derived_merge不现实,也就只能另想他法了,查询之所以慢的原因主要还是因为索引没有命中导致的,也就是说解决了索引命中的问题,就能解决查询慢的问题,先对比2个库命中的索引,发现主要是链表查询的时候ON后面跟的条件在新库上面没有命中索引,ON后面的条件在主表是跟其他列有组成联合索引的,而被链接的表有部分表是跟其他列组合成联合索引,有些表的列则没有任何索引,于是尝试着在被链接的表创建ON后面的字段单独的索引,创建之后,速度明显快了一倍,但是还是有部分索引没有命中,所以又在主表对ON后面的字段单独创建索引(如果ON后面有几个条件,就创建联合索引),创建完成后,执行语句,秒查询出来,问题解决

尝试在旧库上优化索引

由于新库创建了索引后速度上明细比旧库快了很多,当然跟配置本身也有关系,于是相到再旧库上也创建同样的索引会不会更快了,于是在旧库上创建了跟新库相同的索引,执行SQL语句,比未创建索引之前慢了一倍,查看索引命中情况,虽然命中了更多的索引,但是也导致了命中的索引的行数增加

感悟

MySQL不同的版本有不同的SQL优化器,而且不同的版本可能会出现索引命中规则不同,另外索引并不是越多查询就真的能更快,不合理的索引创建不仅会导致插入慢,还会导致查询变慢,所以了解MySQL索引命中规则和了解所用的MySQL的SQL优化器是有必要的,以及不要轻易更新版本,天知道会出现什么莫名其妙的问题。。。。。

原文地址:http://blog.51cto.com/13952955/2294262

时间: 2024-11-03 21:04:50

记一次MySQL迁移并从MySQL5.6升级到5.7后查询慢了几十倍的问题的相关文章

Mysql5.5升级到5.7后MySQLdb不能正常使用的问题解决

ubuntu系统 报错信息1 Type "help", "copyright", "credits" or "license" for more information. >>> import MySQLdb Traceback (most recent call last): File "<stdin>", line 1, in <module> File &qu

mysql从5.6升级到5.7后出现 Expression #1 of ORDER BY clause is not in SELECT list,this is incompatible with DISTINCT

[问题]mysql从5.6升级到5.7后出现:插入数据和修改数据时出错Caused by: com.ibatis.common.jdbc.exception.NestedSQLException: --- The error occurred while applying a parameter map. --- Check the findOrderList-InlineParameterMap. --- Check the statement (query failed). --- Caus

【夯实Mysql基础】记一次mysql语句的优化过程!

1. [事件起因] 今天在做项目的时候,发现提供给客户端的接口时间很慢,达到了2秒多,我第一时间,抓了接口,看了运行的sql,发现就是 2个sql慢,分别占了1秒多. 一个sql是 链接了5个表同时使用了 2个 order by和 1个limit的分页 sql. 一个sql是上一个sql的count(*),即链接了5个表,当然没有limit了(取总数). 2. [着手优化] 1)[优化思路] 第一条是 做client调用 service层的数据缓存 第二条就是 优化sql本身. 这里着重讲一下

数据库 SQL Server 到 MySQL 迁移方法总结

最近接手一起老项目数据库 SQL Server 到 MySQL 的迁移.因此迁移前进行了一些调查和总结.下面是一些 SQL Server 到 MySQL 的迁移方法. 1. 使用 SQLyog 迁移 具体方法可以参加:http://www.cnblogs.com/gaizai/p/3237907.html 优点:该迁移方法很简单,灵活,迁移时,可以进行字段的修改,比如在sql server中原来是datetime,然后迁移到mysql时你可以配置成timestamp: 成功率很高: 缺点:迁移很

Linux 上从 MySQL 迁移到 MariaDB 的简单步骤

大家好!这是一篇介绍如何在服务器或个人电脑上从MySQL迁移到MariaDB的教程.也许你会问为什么我们要将数据库管理从MySQL迁移到MariaDB.往下看我们告诉你为什么这样做.为什么要用MariaDB来代替MySQLMariaDB是MySQL社区开发的分支,也是一个增强型的替代品.它由MySQL前开发者们带头组织的基金会开发,使用起来和MySQL完全一样.自从Oracle买下了MySQL,它就不再自由开源了,但是MariaDB仍然自由开源.一些如谷歌.维基.LinkedIn.Mozilla

在Linux中怎样将MySQL迁移到 MariaDB 上

自从甲骨文收购 MySQL 后,由于甲骨文对 MySQL 的开发和维护更多倾向于闭门的立场,很多 MySQL 的开发者和用户放弃了 MySQL.在社区驱动下,促使更多人移到 MySQL 的另一个叫 MariaDB 的分支.在原有 MySQL 开发人员的带领下,MariaDB 的开发遵循开源的理念,并确保它的二进制格式与 MySQL 兼容.Linux 发行版如 Red Hat 家族(Fedora,CentOS,RHEL),Ubuntu 和 Mint,openSUSE 和 Debian 已经开始使用

怎样将 MySQL 迁移到 MariaDB 上

自从甲骨文收购 MySQL 后,由于甲骨文对 MySQL 的开发和维护更多倾向于闭门的立场,很多 MySQL 的开发者和用户放弃了它.在社区驱动下,促使更多人移到 MySQL 的另一个叫 MariaDB 的分支,在原有 MySQL 开发人员的带领下,MariaDB 的开发遵循开源的理念,并确保它的二进制格式与 MySQL 兼容.众多Linux 发行版例如Red Hat等都支持 MariaDB 作为 MySQL 的直接替换品.如果你想要将 MySQL 中的数据库迁移到 MariaDB 中,那么非常

将Uhost上的MySQL迁移到UDB

一 应用场景描述 最近我们部署在Ucloud的一台云主机由于底层硬件故障,Ucloud建议将这台云主机作动态迁移,但是由于这台服务器上部署有MySQL数据库,数据量太大,云主机迁移会很慢.所以我们需要先将这台云主机的MySQL迁移到UDB,然后切换程序里面的MySQL配置. 二 具体操作步骤 1.将Uhost上的MySQL导出一份 mysqldump  -A --ignore-table=mysql.slow_log  --master-data=1 --single-transaction -

mysql迁移

需要进行线下环境测试,需要拷贝出部分数据出来,即需要进行 mysql 迁移,网上搜索了下资料,大都含糊不清.使用英文关键字google之后,很快明白. 迁移分为"部分迁移"和"全部迁移",一般均使用mysqldump命令,命令用的好,基本可以解决一切问题. 使用范例: mysqldump -h st01-db-product-mt-99.st01 -P5511 -unick -pnick --opt oped_nick_virgo task_item_relatio