SQL优化之踩过的坑【一】
正看资料看的过瘾,突然收到报警,说服务器负载太高,好吧,登录服务器看看,我擦嘞,还能不能愉快的玩耍了?下面是当时的负载情况
看见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优化之踩过的坑【一】的更多相关文章
- SQL查询时踩得一些坑
1.左右连接: left join:LEFT JOIN返回左表的全部行和右表满足ON条件的行,如果左表的行在右表中没有匹配,那么这一行右表中对应数据用NULL代替. inner join: 内连接是最 ...
- 创建优化的Go镜像文件以及踩过的坑
在Docker上创建Go镜像文件并不困难,但建立的文件很大,接近1G,使用起来不太方便.Docker镜像的一个主要难题就是如何优化,创建小的镜像.我们可以用多级构建的方法来创建Docker镜像文件,它 ...
- 当谈 SQL 优化时谈些什么?
欢迎大家关注腾讯云技术社区-博客园官方主页,我们将持续在博客园为大家推荐技术精品文章哦~ 作者:孙银行 背景 Mysql数据库作为数据持久化的存储系统,在实际业务中应用广泛.在应用也经常会因为SQL遇 ...
- 聊聊数据库~4.SQL优化篇
1.5.查询的艺术 上期回顾:https://www.cnblogs.com/dotnetcrazy/p/10399838.html 本节脚本:https://github.com/lotapp/Ba ...
- 当我们谈 SQL 优化时在谈些什么?
作者 |孙银行编辑 | 顾乡 背景 Mysql数据库作为数据持久化的存储系统,在实际业务中应用广泛.在应用也经常会因为SQL遇到各种各样的瓶颈.最常用的Mysql引擎是innodb,索引类型是B-Tr ...
- 《C++之那些年踩过的坑(二)》
C++之那些年踩过的坑(二) 作者:刘俊延(Alinshans) 本系列文章针对我在写C++代码的过程中,尤其是做自己的项目时,踩过的各种坑.以此作为给自己的警惕. 今天讲一个小点,虽然小,但如果没有 ...
- 《C++之那些年踩过的坑(附录一)》
C++之那些年踩过的坑(附录一) 作者:刘俊延(Alinshans) 本系列文章针对我在写C++代码的过程中,尤其是做自己的项目时,踩过的各种坑.以此作为给自己的警惕. [版权声明]转载请注明原文来自 ...
- 浅谈SQL优化入门:3、利用索引
0.写在前面的话 关于索引的内容本来是想写的,大概收集了下资料,发现并没有想象中的简单,又不想总结了,纠结了一下,决定就大概写点浅显的,好吧,就是懒,先挖个浅坑,以后再挖深一点.最基本的使用很简单,直 ...
- 浅谈SQL优化入门:2、等值连接和EXPLAIN(MySQL)
1.等值连接:显性连接和隐性连接 在<MySQL必知必会>中对于等值连接有提到两种方式,第一种是直接在WHERE子句中规定如何关联即可,那么第二种则是使用INNER JOIN关键字.如下例 ...
随机推荐
- HAVANA 团队简介
在Ensembl 下载的gtf 文件中,会有一部分来源自 HAVANA havana 的全称叫做 human and vertebrate analysis and annotation, 是sag ...
- Sql Server 数据类型与 C# 数据类型对照
Sql Server 数据类型与 C# 数据类型对照 已验证类型(Sql Server 2012 & Visual Studio 2013) Sql Server C# 简写 bigint S ...
- SpringBoot 拦截器(Interceptor)的使用
拦截器intercprot 和 过滤器 Filter 其实作用类似 在最开始接触java 使用struts2的时候,里面都是filter 后来springmvc时就用interceptor 没太在意 ...
- [Tensorflow] Cookbook - Neural Network
In this chapter, we'll cover the following recipes: Implementing Operational Gates Working with Gate ...
- 转自ruby迷: 使用Net::SSH和Net::SCP编写Linux服务器管理脚本
试了一下perl下安装ssh模块,整了半天linux/window上都装不上,各依赖模块的版本总是匹配不上,后改了一下思路,用ruby吧 Net::SSH和Net::SCP是两个Ruby操作SSH的g ...
- modbus ASCII和MODBUS RTU区别
下表是MODBUS ASCII协议和RTU协议的比较: 协议 开始标记 结束标记 校验 传输效率 程序处理 ASCII :(冒号) CR,LF LRC 低 直观,简单,易调试 RTU 无 无 CRC ...
- web站点健康检测和告警小脚本
#!/bin/sh web01="http://172.18.52.xx:8080/web/api/getTime" web02="http://172.18.52.xx ...
- favicon.ico 网站小图标标识
随便打开一个网页:比如 http://www.baidu.com/ 可以看到在浏览器的标签头上面显示了一个图标,这个图标是:,也就是我们常说的favicon.ico. 由于这篇文章主要讨论favico ...
- Maven知识点积累一
配置maven变量,变量名可以是:MAVEN_HOME 或 M2_HOME settings.xml配置本地仓库地址: <localRepository>G:/.m2/repository ...
- Codeforces 698A - Vacations - [简单DP]
题目链接:http://codeforces.com/problemset/problem/698/A 题意: 有 $n$ 天假期,每天有四种情况:0.体育馆不开门,没有比赛:1.体育馆不开门,有比赛 ...