zoukankan      html  css  js  c++  java
  • 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版本以前的子查询的性能实在不怎么样。

  • 相关阅读:
    模拟http请求 带 chunked解析办法一
    DLL入口函数
    修复吾爱OD数据窗口双击不出现偏移问题
    PE导入表分析
    持仓盈亏公式
    hadoop工作相关
    zookeeper常用命令
    git使用命令行上传文件
    redis中各种数据类型对应的jedis操作命令
    volatile关键字比较好的解释
  • 原文地址:https://www.cnblogs.com/gomysql/p/4105568.html
Copyright © 2011-2022 走看看