最近在线上发现很多性能有问题的sql,开发写sql语句的时候,没充分考虑是否用上索引了,所以这个坑得DBA来填,好了,废话不多说,把一些线上的优化经验跟大家分享。

由于是线上的表,所以就不公开具体的表结构了,请大家体谅,我会模拟一个类似的表来说明当时的性能问题:

当时的表结构类似此表:

mysql> show create table test\G
*************************** 1. row ***************************
Table: test
Create Table: CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`aa_id` int(11) DEFAULT NULL,
`dealername` varchar(45) DEFAULT NULL,
`dealertype` int(2) DEFAULT NULL,
`bb_id` int(11) NOT NULL,
`membername` varchar(45) DEFAULT NULL,
`createat` datetime DEFAULT NULL,
`creator_id` int(11) DEFAULT NULL,
`name` varchar(45) DEFAULT NULL,
`comp_id` int(11) DEFAULT NULL,
`companyname` varchar(45) DEFAULT NULL,
`cc_id` int(11) DEFAULT NULL,
`level_id` int(2) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `shopmember_unique` (`aa_id`,`bb_id`,`cc_id`) USING BTREE
) ENGINE=MyISAM AUTO_INCREMENT=301554 DEFAULT CHARSET=utf8
1 row in set (0.00 sec) mysql>

当时查看Lepus 的慢查询监控,看到大量的这类SQL语句,而且消耗时长有点长:

大量类似以下的SQL语句:

select aa_id,dealername,dealertype,membername from  test where level_id <=4 order by aa_id limit 243000, 100;

下面我们看一下SQL语句的执行计划:

mysql> explain select aa_id,dealername,dealertype,membername from  test where level_id <=4 order by aa_id limit 243000, 100;
+----+-------------+-------+------+---------------+------+---------+------+--------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+--------+-----------------------------+
| 1 | SIMPLE | test | ALL | NULL | NULL | NULL | NULL | 301508 | Using where; Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+--------+-----------------------------+
1 row in set (0.00 sec)

很多人一看表结构,发现在列level_id没索引,添加一个普通索引就完事啦!下面我们来试试:

mysql> alter table test add key (level_id);
Query OK, 301508 rows affected (3.71 sec)
Records: 301508 Duplicates: 0 Warnings: 0 mysql> explain select * from test where level_id <=4 order by aa_id limit 243000, 100;
+----+-------------+-------+-------+---------------+----------+---------+------+--------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+----------+---------+------+--------+-----------------------------+
| 1 | SIMPLE | test | range | level_id | level_id | 4 | NULL | 301393 | Using where; Using filesort |
+----+-------------+-------+-------+---------------+----------+---------+------+--------+-----------------------------+
1 row in set (0.00 sec) mysql>

添加索引后,可以看到用上索引了,但效果相对之前并没有很大的提升,还有些人可能会说,在level_id和aa_id添加组合索引,性能可能就好了,我们再来看下:

mysql> alter table test add key (level_id,aa_id);
Query OK, 301508 rows affected (3.75 sec)
Records: 301508 Duplicates: 0 Warnings: 0 mysql> explain select * from test where level_id <=4 order by aa_id limit 243000, 100;
+----+-------------+-------+-------+---------------+----------+---------+------+--------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+----------+---------+------+--------+-----------------------------+
| 1 | SIMPLE | test | range | level_id | level_id | 4 | NULL | 301218 | Using where; Using filesort |
+----+-------------+-------+-------+---------------+----------+---------+------+--------+-----------------------------+
1 row in set (0.00 sec) mysql>

可以看到,效果还是一样的差。为什么显示用上了索引,却还扫描了几十万行呢?

我们回顾一下不会用上索引的几种情况:(可以参考我的之前写的常用SQL语句优化

• 两个表关联字段类型不一样(也包括长度不一样)
• 通过索引扫描的记录数超过30%,变成全表扫描
• 联合索引中,第一个索引列使用范围查询,且返回数据超表总数据的30%
• 联合索引中,第一个查询条件不是最左索引列
• 模糊查询条件列最左以通配符 % 开始
• 内存表(HEAP 表)使用HASH索引时,使用范围检索或者ORDER BY
• 两个独立索引,其中一个用于检索,一个用于排序(只能用到部份)
• 使用了不同的 ORDER BY 和 GROUP BY 表达式

上面的SQL语句,符合了上面的联合索引中,第一个索引使用范围查询所以用不上索引,我们直接查询看看用时为多少:

mysql> select * from test where level_id <=4 order by aa_id limit 243000, 100;
100 rows in set (1.63 sec)

没用上索引,那我们应该怎么优化它呢?我们应该用延迟关联的思想,把sql语句修改为如下:

写法一:

mysql> reset query cache;
Query OK, 0 rows affected (0.00 sec) mysql> explain SELECT a.* FROM test a,(select id from test where level_id <=4 order by aa_id limit 243000, 100) b where a.id=b.id ;
+----+-------------+------------+--------+---------------+----------+---------+------+--------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+--------+---------------+----------+---------+------+--------+-----------------------------+
| 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 100 | |
| 1 | PRIMARY | a | eq_ref | PRIMARY | PRIMARY | 4 | b.id | 1 | |
| 2 | DERIVED | test | range | level_id | level_id | 4 | NULL | 301218 | Using where; Using filesort |
+----+-------------+------------+--------+---------------+----------+---------+------+--------+-----------------------------+
3 rows in set (0.30 sec) mysql> SELECT a.* FROM test a,(select id from test where level_id <=4 order by aa_id limit 243000, 100) b where a.id=b.id ; 100 rows in set (0.30 sec)

可以看到速度快了几倍,现在数据量只有几十万,如果几百万,效果会更明显,为什么这样写会比之前的效果好呢?因为延迟关联通过覆盖索引返回所需数据行的主键,再根据主键关联原表获得需要的数据,所以速度比之前快上不少。

写法二:

mysql> EXPLAIN SELECT * FROM `test` INNER JOIN ( SELECT id FROM `test` where level_id <=4 order by aa_id limit 243000, 100) t2 USING (id);
+----+-------------+------------+--------+---------------+----------+---------+-------+--------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+--------+---------------+----------+---------+-------+--------+-----------------------------+
| 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 100 | |
| 1 | PRIMARY | test | eq_ref | PRIMARY | PRIMARY | 4 | t2.id | 1 | |
| 2 | DERIVED | test | range | level_id | level_id | 4 | NULL | 301218 | Using where; Using filesort |
+----+-------------+------------+--------+---------------+----------+---------+-------+--------+-----------------------------+
3 rows in set (0.30 sec) mysql>

优化案例二:

表结构是:

mysql> show create table test2\G
*************************** 1. row ***************************
Table: test2
Create Table: CREATE TABLE `test2` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) DEFAULT NULL,
`code` varchar(32) DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
`status` int(2) DEFAULT '',
`createat` datetime DEFAULT NULL,
`write_id` int(11) DEFAULT NULL,
`creator_id` int(11) DEFAULT NULL,
`dealer_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `code_index` (`code`) USING BTREE,
KEY `dealer_id` (`dealer_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7014142 DEFAULT CHARSET=utf8
1 row in set (0.00 sec) mysql>

slowlog里有大量这样的查询:

select count( id ) from `test2` where createat between '2015-05-26 00:00:00' and '2015-05-26 23:59:59'  and status not in(7) and creator_id=8774 and write_id=925;          

查看下执行计划:

mysql> explain select count( id ) from `test2` where createat between '2015-05-26 00:00:00' and '2015-05-26 23:59:59'  and status not in(7) and creator_id=8774 and write_id=925;
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
| 1 | SIMPLE | test2 | ALL | NULL | NULL | NULL | NULL | 5135067 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
1 row in set (0.00 sec) mysql>

没有索引,做了全表扫描,有些开发人员创建表的时候考虑得不周到,导致频繁出现影响性能的sql,我们添加组合索引看看效果(这里要注意一下,在线上如果是5.6以下的版本,对于一些大数据的表,别直接添加索引,因为这个过程会阻塞DML操作的,如果添加索引需要的时间是几个小时或者更多,这是很悲剧的一件事情,个人经验,小数据的表发现没索引,或者索引设置的不合理,直接alter修改,大数据的表,就要用pt工具了。5.6版本的MySQL虽然支持了Online DDL,但也添加索引的时候,要考虑是否处于业务的高峰期,尽量选择业务量不繁忙的时候添加):

mysql> alter table test2 add key (createat,status);
Query OK, 0 rows affected (1 min 36.27 sec)
Records: 0 Duplicates: 0 Warnings: 0 mysql> explain select count( id ) from `test2` where createat between '2015-05-26 00:00:00' and '2015-05-26 23:59:59' and status not in(7) and creator_id=8774 and write_id=925;
+----+-------------+-------+-------+---------------+----------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+----------+---------+------+------+--------------------------+
| 1 | SIMPLE | test2 | range | createat | createat | 14 | NULL | 1 | Using where; Using index |
+----+-------------+-------+-------+---------------+----------+---------+------+------+--------------------------+
1 row in set (0.00 sec) mysql>

可以看到已经用上索引了,Using index说明用上了覆盖索引,覆盖索引(只访问索引的查询,即查询只需要访问索引,而无须访问数据行,最简单的理解,比如翻开一本书,从目录页查找某些内容,但是目录就写的比较详细,我们在目录就找到了自己想看的内容)

而且返回在的行数,差距有多大,相信大家一眼就看到了。

例如:SELECT * FROM t WHERE col1 = ? AND col2 >= ? AND col3 = ?; 这时候,只能用到 idx 索引的最左2列进行检索,而col3条件则无法利用索引进行检索,所以上面只添加组合索引为前两列的值。

案例三

多表联接并且有排序时,排序字段必须是驱动表里的,否则排序列无法用到索引;

表os_diskio_history的表结构:

mysql> show create table os_diskio_history \G
*************************** 1. row ***************************
Table: os_diskio_history
Create Table: CREATE TABLE `os_diskio_history` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`ip` varchar(50) NOT NULL,
`tags` varchar(100) DEFAULT NULL,
`fdisk` varchar(50) NOT NULL DEFAULT '',
`disk_io_reads` bigint(18) NOT NULL DEFAULT '',
`disk_io_writes` bigint(18) NOT NULL DEFAULT '',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`YmdHi` bigint(10) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `idx_ymdhi` (`YmdHi`) USING BTREE,
KEY `idx_ip_ymdhi` (`ip`,`YmdHi`),
KEY `idx_io_reads` (`disk_io_reads`),
KEY `idx_io_writes` (`disk_io_writes`)
) ENGINE=InnoDB AUTO_INCREMENT=3550043 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

表os_diskio的表结构:

mysql> show create table os_diskio\G
*************************** 1. row ***************************
Table: os_diskio
Create Table: CREATE TABLE `os_diskio` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`ip` varchar(50) NOT NULL,
`tags` varchar(100) DEFAULT NULL,
`fdisk` varchar(50) NOT NULL DEFAULT '',
`disk_io_reads` bigint(18) NOT NULL DEFAULT '',
`disk_io_writes` bigint(18) NOT NULL DEFAULT '',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3550092 DEFAULT CHARSET=utf8
1 row in set (0.00 sec) mysql>

优化前的执行计划如下:

mysql> explain select a.* from  os_diskio_history a inner join os_diskio b on a.id=b.id order by a.id desc limit 10;
+----+-------------+-------+--------+---------------+---------+---------+------------+------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+---------------+---------+---------+------------+------+----------------------------------------------+
| 1 | SIMPLE | b | index | PRIMARY | PRIMARY | 4 | NULL | 19 | Using index; Using temporary; Using filesort |
| 1 | SIMPLE | a | eq_ref | PRIMARY | PRIMARY | 4 | lepus.b.id | 1 | |
+----+-------------+-------+--------+---------------+---------+---------+------------+------+----------------------------------------------+
2 rows in set (0.00 sec) mysql>

sql重构后,把连接方式改成把连接方式改成了「STRAIGHT_JOIN」的执行计划如下:

mysql> explain select a.* from  os_diskio_history a straight_join os_diskio b on a.id=b.id order by a.id desc limit 10;
+----+-------------+-------+--------+---------------+---------+---------+------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+---------------+---------+---------+------------+------+-------------+
| 1 | SIMPLE | a | index | PRIMARY | PRIMARY | 4 | NULL | 10 | |
| 1 | SIMPLE | b | eq_ref | PRIMARY | PRIMARY | 4 | lepus.a.id | 1 | Using index |
+----+-------------+-------+--------+---------------+---------+---------+------------+------+-------------+
2 rows in set (0.00 sec) mysql>

可以看到性能是提升的,由于数据量不大,这里只能举个例子说明,嘻嘻,期待大家探讨

总结:一、定期采集slow query log,用pt-query-digest工具进行分析,可结合Anemometer系统进行slow query管理以便分析slow query并进行后续优化工作;也可以用开源的Lepus监控系统,可以让你清楚明了的发现那些sql执行用的时长,执行的次数等

二、当发现表没有适当的索引时,要添加索引的时候,要考虑数据里的大小及业务的繁忙程度再添加

三、给开发适当的MySQL培训,让开发慢慢改善,毕竟DBA对业务逻辑这块没开发熟悉

参考资料:http://blog.itpub.net/22664653/viewspace-1176153/

http://imysql.com/2014/07/26/mysql-optimization-case-paging-optimize.shtml

http://t.cn/R2IcTMi

作者:陆炫志

出处:xuanzhi的博客 http://www.cnblogs.com/xuanzhi201111

您的支持是对博主最大的鼓励,感谢您的认真阅读。本文版权归作者所有,欢迎转载,但请保留该声明。

 
 
 

线上SQL优化的更多相关文章

  1. 线上mysql内存持续增长直至内存溢出被killed分析(已解决)

    来新公司前,领导就说了,线上生产环境Mysql库经常会发生日间内存爆掉被killed的情况,结果来到这第一天,第一件事就是要根据线上服务器配置优化配置,同时必须找出现在mysql内存持续增加爆掉的原因 ...

  2. 转://从一条巨慢SQL看基于Oracle的SQL优化

    http://mp.weixin.qq.com/s/DkIPwbDKIjH2FMN13GkT4w 本次分享的内容是基于Oracle的SQL优化,以一条巨慢的SQL为例,从快速解读SQL执行计划.如何从 ...

  3. 从一条巨慢SQL看基于Oracle的SQL优化(重磅彩蛋+PPT)

    本文根据DBAplus社群第110期线上分享整理而成,文末还有好书送哦~ 讲师介绍 丁俊 新炬网络首席性能优化专家 SQL审核产品经理 DBAplus社群联合发起人.<剑破冰山-Oracle开发 ...

  4. MySQL慢日志线上问题分析及功能优化

    本文来源于数据库内核专栏. MySQL慢日志(slow log)是MySQL DBA及其他开发.运维人员需经常关注的一类信息.使用慢日志可找出执行时间较长或未走索引等SQL语句,为进行系统调优提供依据 ...

  5. 记一次线上 OOM 和性能优化

    大家好,我是鸭血粉丝(大家会亲切的喊我 「阿粉」),是一位喜欢吃鸭血粉丝的程序员,回想起之前线上出现 OOM 的场景,毕竟当时是第一次遇到这么 紧脏 的大事,要好好记录下来. 1 事情回顾 在某次周五 ...

  6. 转:一篇讲线上优化查 CPU的脚本

    原文链接:https://my.oschina.net/leejun2005/blog/1524687   摘要: 本文主要针对 Java 服务而言 0.背景 经常做后端服务开发的同学,或多或少都遇到 ...

  7. 史上最全存储引擎、索引使用及SQL优化的实践

    史上最全存储引擎.索引使用及SQL优化的实践 1 MySQL的体系结构概述 2. 存储引擎 2.1 存储引擎概述 2.2 各种存储引擎特性 2.2.1 InnoDB 2.2.2 MyISAM 3. 优 ...

  8. 大厂面试经:说一下你们线上JVM是如何优化的?

    JVM(Java虚拟机)简单来说就是运行Java代码的解释器,作为螺丝钉程序员JVM其实了解下就差不多啦,不懂JVM内部细节照样能写出优质的代码!但是一到造火箭.飞机的场景(面试)不懂JVM的你,会被 ...

  9. 一个SQL注释引发的线上问题

    最近开始服务拆分,时间将近半个月.测试阶段也非常顺利,没有什么问题. 但上线之后的第二天,产品就风风火火的来找我们了,一看就是线上有什么问题.我们也不敢说,我们也不敢问,线上的后台商品忽然无法上架了, ...

随机推荐

  1. Python的文件读写

    目录 读文件 操作文件 读取内容 面试题的例子 写文件 操作模式 指针操作 字符编码 读文件 操作文件 打开一个文件用open()方法(open()返回一个文件对象,它是可迭代的): 文件使用完毕后必 ...

  2. ELK环境配置

    一.安装java环境 1.下载jre并安装,安装过程中没有什么特殊的,一直默认下一步即可. 2.配置环境变量 其中变量值为我们安装的jre的路径 二.安装elasticsearch 1.下载es安装包 ...

  3. [代码]--给GridControl中的某列添加图片

    要让GridControl的某列显示图片只需要数据源中有图片就可以正确显示 1.给DataSet添加一列,格式为image ds.Tables[].Columns.Add("SIGN&quo ...

  4. MT【95】由参数前系数凑配系数题2

    提示:都是看$a,b$前的系数做的$a=4/3,b=2/3;a+b=\le2$,一样的可以求得$a+b$的最小值-1,当$b=\frac{1}{3},a=\frac{-4}{3}$时取到等号.此题是清 ...

  5. 【BZOJ1485】[HNOI2009]有趣的数列(组合数学)

    [BZOJ1485][HNOI2009]有趣的数列(组合数学) 题面 BZOJ 洛谷 题解 从小往大填数,要么填在最小的奇数位置,要么填在最小的偶数位置. 偶数位置填的数的个数不能超过奇数位置填的数的 ...

  6. CF888G Xor-MST 解题报告

    CF888G Xor-MST 题意翻译 给定一个\(n\)个节点的完全图,每个节点有个编号\(a_i\),节点\(i\)和节点\(j\)之间边的权值为\(a_i\ xor\ a_j\),求该图的最小生 ...

  7. [USACO18OPEN]Out of Sorts G 冒泡排序理解之一

    题目描述 给一个双向冒泡排序的程序: moo表示输出moo sorted = false while (not sorted): sorted = true moo to N-: ] < A[i ...

  8. 用rem来做响应式开发(转)

    由于最近在做公司移动项目的重构,因为要实现响应式的开发,所以大量使用到了rem的单位,觉得这个单位有点意思.但是现在貌似用他的人很少.上一篇文章我分享了淘宝写的一篇rem的介绍,介绍的非常全面,但是他 ...

  9. Java基础-Java中的并法库之重入读写锁(ReentrantReadWriteLock)

    Java基础-Java中的并法库之重入读写锁(ReentrantReadWriteLock) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 在学习Java的之前,你可能已经听说过读 ...

  10. Linux iptables常用命令的使用

    为什么会有本文 因为最近帮一个朋友布署一个上网梯子,他那边本来用的是v2ray,但是他想用ssr,但是安装配置ssr过程中出了很多问题,比如linux内核版本4.9有点老,不支持bbr加速.无法连接s ...