正看资料看的过瘾,突然收到报警,说服务器负载太高,好吧,登录服务器看看,我擦嘞,还能不能愉快的玩耍了?下面是当时的负载情况

看见mysql使用cpu已经到了2000,io没有等待。说明应该没有大的临时表,或者文件排序,但是SQL语句肯定还是有问题的,好吧,那进数据库看看到底在干嘛,执行show full processlist后,发现有好几百个连接在执行同一条SQL语句,看见SQL也还好,不复杂,是子查询,我最恶心的子查询。那就EXPLAIN一下咯,线上的东东我就不在这里贴出来了,后面我会创建类似的表来解释。

表结构简单如下:

mysql> desc t1;
+---------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| role_id | int(11) | NO | | 0 | |
| referer | varchar(20) | NO | | | |
+---------+-------------+------+-----+---------+----------------+
3 rows in set (0.00 sec) mysql> desc t2;
+--------------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| role_id | int(11) | NO | MUL | 0 | |
| privilege_id | int(11) | NO | | 0 | |
+--------------+---------+------+-----+---------+----------------+
3 rows in set (0.00 sec) mysql>

索引如下:

mysql> show index from t1;
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| t1 | 0 | PRIMARY | 1 | id | A | 329 | NULL | NULL | | BTREE | | |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
1 row in set (0.00 sec) mysql> show index from t2;
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| t2 | 0 | PRIMARY | 1 | id | A | 12826 | NULL | NULL | | BTREE | | |
| t2 | 1 | role_id | 1 | role_id | A | 583 | NULL | NULL | | BTREE | | |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
2 rows in set (0.00 sec) mysql>

当时大量SQL语句如下:

mysql> select privilege_id from t2 where role_id in (select role_id from t1 where id=193); 

那么你会觉得这语句有问题么?你会说这哪有什么问题啊。t2表role_id有索引,t1的表id是主键,肯定走索引啦!但是就是这么坑,那我们EXPLAIN一下,结果如下:

mysql> explain select privilege_id from t2 where role_id in (select role_id from t1 where id=193);
+----+--------------------+-------+-------+---------------+---------+---------+-------+-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+-------+-------+---------------+---------+---------+-------+-------+-------------+
| 1 | PRIMARY | t2 | ALL | NULL | NULL | NULL | NULL | 12826 | Using where |
| 2 | DEPENDENT SUBQUERY | t1 | const | PRIMARY | PRIMARY | 4 | const | 1 | |
+----+--------------------+-------+-------+---------------+---------+---------+-------+-------+-------------+
2 rows in set (0.00 sec) mysql>

what?发生了什么事情,怎么和想的不一样?这不科学啊。虽然数据量不多,但是执行频率非常高的情况下,也是一种悲剧。好吧,我本来就不喜欢子查询,我改成了join看看。

mysql> explain select a.privilege_id from t2 as a inner join t1 as b on a.role_id=b.role_id and b.id=193;
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| 1 | SIMPLE | b | const | PRIMARY | PRIMARY | 4 | const | 1 | |
| 1 | SIMPLE | a | ref | role_id | role_id | 4 | const | 128 | |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
2 rows in set (0.00 sec) mysql>

这下更郁闷了,怎么join就走索引了呢?后来想到in后面接受多个值,但是我的t1表的id是主键肯定只有一条记录,那么我可以改成=,那么我们试试。

mysql> explain select privilege_id from t2 where role_id = (select role_id from t1 where id=193);
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
| 1 | PRIMARY | t2 | ref | role_id | role_id | 4 | const | 128 | Using where |
| 2 | SUBQUERY | t1 | const | PRIMARY | PRIMARY | 4 | | 1 | |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
2 rows in set (0.00 sec) mysql>

我去,这样真的可以走索引,好吧。晕了。。这时突然想到或许优化器的问题,还是一个朋友提醒,这下才明白。原来子查询这里role_id()有限制,这个括号里的查询要基于唯一索引或是主键。不过在更高版本已经修复了这个问题。下面给出例子。
percoan-5.5.38的版本

mysql> show variables like '%version';
+------------------+-------------+
| Variable_name | Value |
+------------------+-------------+
| innodb_version | 5.5.38-35.2 |
| protocol_version | 10 |
| version | 5.5.38-35.2 |
+------------------+-------------+
3 rows in set (0.01 sec) mysql> explain select privilege_id from t2 where role_id in (select role_id from t1 where id=193);
+----+--------------------+-------+-------+---------------+---------+---------+-------+-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+-------+-------+---------------+---------+---------+-------+-------+-------------+
| 1 | PRIMARY | t2 | ALL | NULL | NULL | NULL | NULL | 12826 | Using where |
| 2 | DEPENDENT SUBQUERY | t1 | const | PRIMARY | PRIMARY | 4 | const | 1 | |
+----+--------------------+-------+-------+---------------+---------+---------+-------+-------+-------------+
2 rows in set (0.00 sec) mysql>

percona-5.6.21版本

[root@localhost [test]> show  variables like '%version';
+------------------+-----------------+
| Variable_name | Value |
+------------------+-----------------+
| innodb_version | 5.6.21-rel70.0 |
| protocol_version | 10 |
| version | 5.6.21-70.0-log |
+------------------+-----------------+
3 rows in set (0.00 sec) [root@localhost [test]> explain select privilege_id from t2 where role_id in (select role_id from t1 where id=193);
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| 1 | SIMPLE | t1 | const | PRIMARY | PRIMARY | 4 | const | 1 | NULL |
| 1 | SIMPLE | t2 | ref | role_id | role_id | 4 | const | 129 | NULL |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
2 rows in set (0.00 sec) [root@localhost [test]>

可以看见5.6.21的版本已经不受影响了。后面让开发同学修改成join以后,负载慢慢的下来了。下面是zabbix监控到的负载情况:

总结:

子查询虽然写起来方便,且简单易懂,但是我们还是尽量的使用join,因为在5.6版本以前的子查询的性能实在不怎么样。

SQL优化之踩过的坑【一】的更多相关文章

  1. SQL查询时踩得一些坑

    1.左右连接: left join:LEFT JOIN返回左表的全部行和右表满足ON条件的行,如果左表的行在右表中没有匹配,那么这一行右表中对应数据用NULL代替. inner join: 内连接是最 ...

  2. 创建优化的Go镜像文件以及踩过的坑

    在Docker上创建Go镜像文件并不困难,但建立的文件很大,接近1G,使用起来不太方便.Docker镜像的一个主要难题就是如何优化,创建小的镜像.我们可以用多级构建的方法来创建Docker镜像文件,它 ...

  3. 当谈 SQL 优化时谈些什么?

    欢迎大家关注腾讯云技术社区-博客园官方主页,我们将持续在博客园为大家推荐技术精品文章哦~ 作者:孙银行 背景 Mysql数据库作为数据持久化的存储系统,在实际业务中应用广泛.在应用也经常会因为SQL遇 ...

  4. 聊聊数据库~4.SQL优化篇

    1.5.查询的艺术 上期回顾:https://www.cnblogs.com/dotnetcrazy/p/10399838.html 本节脚本:https://github.com/lotapp/Ba ...

  5. 当我们谈 SQL 优化时在谈些什么?

    作者 |孙银行编辑 | 顾乡 背景 Mysql数据库作为数据持久化的存储系统,在实际业务中应用广泛.在应用也经常会因为SQL遇到各种各样的瓶颈.最常用的Mysql引擎是innodb,索引类型是B-Tr ...

  6. 《C++之那些年踩过的坑(二)》

    C++之那些年踩过的坑(二) 作者:刘俊延(Alinshans) 本系列文章针对我在写C++代码的过程中,尤其是做自己的项目时,踩过的各种坑.以此作为给自己的警惕. 今天讲一个小点,虽然小,但如果没有 ...

  7. 《C++之那些年踩过的坑(附录一)》

    C++之那些年踩过的坑(附录一) 作者:刘俊延(Alinshans) 本系列文章针对我在写C++代码的过程中,尤其是做自己的项目时,踩过的各种坑.以此作为给自己的警惕. [版权声明]转载请注明原文来自 ...

  8. 浅谈SQL优化入门:3、利用索引

    0.写在前面的话 关于索引的内容本来是想写的,大概收集了下资料,发现并没有想象中的简单,又不想总结了,纠结了一下,决定就大概写点浅显的,好吧,就是懒,先挖个浅坑,以后再挖深一点.最基本的使用很简单,直 ...

  9. 浅谈SQL优化入门:2、等值连接和EXPLAIN(MySQL)

    1.等值连接:显性连接和隐性连接 在<MySQL必知必会>中对于等值连接有提到两种方式,第一种是直接在WHERE子句中规定如何关联即可,那么第二种则是使用INNER JOIN关键字.如下例 ...

随机推荐

  1. java对象与map对象相互转换

    /** * 使用org.apache.commons.beanutils进行转换 */ class A { public static Object mapToObject(Map<String ...

  2. 1. ansible-playbook 变量定义与引用

    简单的playbook playbook 是ansible的核心组件,使用的是YAML语法. 下面请看简单的playbook代码 [root@LeoDevops playb]# cat nginx.y ...

  3. 【代码审计】大米CMS_V5.5.3 任意文件删除及代码执行漏洞分析

      0x00 环境准备 大米CMS官网:http://www.damicms.com 网站源码版本:大米CMS_V5.5.3试用版(更新时间:2017-04-15) 程序源码下载:http://www ...

  4. c# 中config.exe 引发的一些问题

    public static void CreateConfig(){ //c#可以添加内置的app.config,我们通过ConfigrationManager类可以 //可以很轻松的操作相关节点,操 ...

  5. collections模块和os模块

    collections模块 在内置数据类型(dict.list.set.tuple)的基础上,collections模块还提供了几个额外的数据类型:Counter.deque.defaultdict. ...

  6. .net core 支持apk下载

    在 app.UseStaticFiles(); 后面加上 app.UseStaticFiles(new StaticFileOptions { //FileProvider = new Physica ...

  7. 传多个id 存入一个容器里,让另一个页面接受并显示数据

    要传页面的id: <ul class="contrast-ul"> <!-- <li>id都在这里面</li> --> </u ...

  8. Python与金融量化分析----金融与量化投资

    一:金融了解 金融:就是对现有资源进行重新的整合之后,进行价值和利润的等效流通. 金融工具: 股票 期货 黄金 外汇 基金 ............. 股票: 股票是股份公司发给出资人多的一种凭证,股 ...

  9. 初学的linux命令行

    这几条命令是今天刚初学的,以前总看别人输入命令,好利落,到自己了,真心觉得难.目前就学了这几个命令.后期等学会了,再进行添加 vm 文件名 ——> 新建文件: :wq  ——>  保存并退 ...

  10. 所生成项目的处理器架构“MSIL”与引用“***”的处理器架构“x86”不匹配。这种不匹配可能会导致运行时失败。请考虑通过配置管理器...

    警告:所生成项目的处理器架构“MSIL”与引用“***”的处理器架构“x86”不匹配.这种不匹配可能会导致运行时失败.请考虑通过配置管理器更改您的项目的目标处理器架构,以使您的项目与引用间的处理器架构 ...