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

看见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. circRNA 中的ALU 重复元件

    circRNA 最初研究的很少,只有很小一部分基因有检测到circRNA, 当时都认为是剪切错误形成的,对于其功能也没人去研究:学者对人类的成纤维细胞进行转录组测序,构建去核糖体文库, 同时采用了RN ...

  2. golang 爬虫

    go语言,goquery,colly,chromedp,webloop等 https://www.cnblogs.com/majianguo/p/8186429.html

  3. [hive] hive 安装、配置

    一.hive安装 1.官网下载 1.2.2版本 http://apache.fayea.com/hive/hive-1.2.2/ 2. 解压,此处目录为 /opt/hadoop/hive-1.2.2 ...

  4. [IR] Huffman Coding

    为了保证:Block中,所有的叶子在所有的中间结点的前面.Static: Huffman coding Dynamic: Adaptive Huffman 一些概念 压缩指标 • Compress a ...

  5. ASP.NET MVC 4 (十三) 基于表单的身份验证

    在前面的章节中我们知道可以在MVC应用程序中使用[Authorize]特性来限制用户对某些网址(控制器/控制器方法)的访问,但这都是在对用户认证之后,而用户的认证则依然是使用ASP.NET平台的认证机 ...

  6. 设置Linux打开文件句柄/proc/sys/fs/file-max和ulimit -n的区别

    max-file 表示系统级别的能够打开的文件句柄的数量.是对整个系统的限制,并不是针对用户的. ulimit -n 控制进程级别能够打开的文件句柄的数量.提供对shell及其启动的进程的可用文件句柄 ...

  7. Flask框架(2)-JinJa2模板

    为了把业务逻辑和表现逻辑分开,Flask把表现逻辑移到JinJa2模板,模板是一个包含响应文本的文件.它用占位变量表示动态部分,其具体要从请求上下文才知道. 把真实值替换掉占位变量成为渲染,JinJa ...

  8. URL中的空格

    如果URL中带空格,在浏览器中可以显示,但是如果访问比如 UIImage 获取图片的时候就会出现BAD URL. 解决: NSString* urlText = @"70.84.58.40/ ...

  9. day_6.17 gevent版服务器

    用协程做并发服务器   gevent版本: monkey.patch_all() 修改了自己的代码 只能用mokey里面的代码 #!--*coding=utf-8*-- #2018-6-17 12:0 ...

  10. 通过Java语言连接mysql数据库

    1加载驱动 2创建链接对象 3创建语句传输对象 4接受结果集 5遍历 6关闭资源