zoukankan      html  css  js  c++  java
  • 【mysql优化】语句优化

    1.int型子查询陷阱

    如下两个表:

    mysql> desc user;
    +----------+-------------+------+-----+---------+-------+
    | Field    | Type        | Null | Key | Default | Extra |
    +----------+-------------+------+-----+---------+-------+
    | id       | int(11)     | NO   | PRI | NULL    |       |
    | name     | varchar(22) | YES  |     | NULL    |       |
    | class_id | int(11)     | YES  | MUL | NULL    |       |
    +----------+-------------+------+-----+---------+-------+
    3 rows in set (0.17 sec)
    
    mysql> desc class;
    +------------+-------------+------+-----+---------+----------------+
    | Field      | Type        | Null | Key | Default | Extra          |
    +------------+-------------+------+-----+---------+----------------+
    | class_id   | int(11)     | NO   | PRI | NULL    | auto_increment |
    | class_name | varchar(20) | YES  |     | 000     |                |
    +------------+-------------+------+-----+---------+----------------+
    2 rows in set (0.15 sec)

    2表的数据量:

    mysql> select count(id) from user;
    +-----------+
    | count(id) |
    +-----------+
    |   1120013 |
    +-----------+
    1 row in set (2.50 sec)
    
    mysql> select count(class_id) from class;
    +-----------------+
    | count(class_id) |
    +-----------------+
    |               5 |
    +-----------------+
    1 row in set (0.10 sec)

         要求查询班级名称是'3'的学生?

    1.用in查询:

    mysql> select id,name from user where class_id in (select class_id from class wh
    ere class_name='3')
        -> ;
    +-----+----------+
    | id  | name     |
    +-----+----------+
    |   3 | QLQ3     |
    |   9 | QLQ9     |
    |  10 | QLQ10    |
    |  12 | QLQ12    |
    |  13 | QLQ13    |
    | 102 | name2100 |
    | 103 | name3100 |
    | 104 | name4100 |
    |   5 | QLQ5     |
    +-----+----------+
    9 rows in set (0.17 sec)

    花费了0.17秒,用explain分析语句:发现也是class表是全局扫描,user表用了class_id索引。

    mysql> explain select id,name from user where class_id in (select class_id from
    class where class_name='3')G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: class
       partitions: NULL
             type: ALL
    possible_keys: PRIMARY
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 5
         filtered: 20.00
            Extra: Using where
    *************************** 2. row ***************************
               id: 1
      select_type: SIMPLE
            table: user
       partitions: NULL
             type: ref
    possible_keys: class_id
              key: class_id
          key_len: 5
              ref: maven.class.class_id
             rows: 1
         filtered: 100.00
            Extra: NULL
    2 rows in set, 1 warning (0.10 sec)

      解释:我们理解的是先查询in里面的班级编号,然后利用班级编号去查询外面的id和name。两个表都是全表扫描。

    2.改为连接查询:

    mysql> SELECT
        ->   u.id,
        ->   u.name
        -> FROM USER AS u
        ->   INNER JOIN (SELECT
        ->                 class_id
        ->               FROM class
        ->               WHERE class_name = '3') AS c
        ->     ON c.class_id = u.class_id;
    +-----+----------+
    | id  | name     |
    +-----+----------+
    |   3 | QLQ3     |
    |   9 | QLQ9     |
    |  10 | QLQ10    |
    |  12 | QLQ12    |
    |  13 | QLQ13    |
    | 102 | name2100 |
    | 103 | name3100 |
    | 104 | name4100 |
    |   5 | QLQ5     |
    +-----+----------+
    9 rows in set (0.11 sec)

    0.11秒

    分析上面语句:发现也是class表是全局扫描,user表用了class_id索引。

    mysql> EXPLAIN
        -> SELECT
        ->   u.id,
        ->   u.name
        -> FROM USER AS u
        ->   INNER JOIN (SELECT
        ->                 class_id
        ->               FROM class
        ->               WHERE class_name = '3') AS c
        ->     ON c.class_id = u.class_id G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: class
       partitions: NULL
             type: ALL
    possible_keys: PRIMARY
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 5
         filtered: 20.00
            Extra: Using where
    *************************** 2. row ***************************
               id: 1
      select_type: SIMPLE
            table: u
       partitions: NULL
             type: ref
    possible_keys: class_id
              key: class_id
          key_len: 5
              ref: maven.class.class_id
             rows: 1
         filtered: 100.00
            Extra: NULL
    2 rows in set, 1 warning (0.10 sec)

    总结:

        也就是说这个mysql5.7的in查询与我们想的一样,就是先查询里面的东西,然后利用里面查到的值去外面查询,所以外面的查询可以用到索引。

    网上总结的经验如下:

    : 在ecshop商城表中,查询6号栏目的商品, (注,6号是一个大栏目)

      最直观的mysql> select goods_id,cat_id,goods_name from goods where cat_id in (select cat_id from ecs_category where parent_id=6);

    误区: 给我们的感觉是, 先查到内层的6号栏目的子栏目,如7,8,9,11

      然后外层, cat_id in (7,8,9,11)

    事实: 如下图, goods表全扫描, 并逐行与category表对照,看parent_id=6是否成立

         

     

    原因: mysql的查询优化器,针对In型做优化,被改成了exists的执行效果.

      当goods表越大时, 查询速度越慢.

    改进: 用连接查询来代替子查询

    explain select goods_id,g.cat_id,g.goods_name from goods as g
    
    inner join (select cat_id from ecs_category where parent_id=6) as t
    
    using(cat_id) G

      内层 select cat_id from ecs_category where parent_id=6 ; 用到Parent_id索引, 返回4行

     

     网上总结的经验是 in 查询不是我们想的那样,而是逐行扫描外层的数据去匹配里面的查询,因此查询效率低下。改进方法是利用inner  join改进。

          网上总结:也就是in不走索引?待思考的问题。。。。。。。。。。。。

            到底是我的实践是错的?还是因为mysql版本问题?

     

    2 exists子查询    用法参考http://www.cnblogs.com/qlqwjy/p/8598091.html

      exists只是返回一个ture或false的结果(这也是为什么子查询里是select 'x'的原因 当然也可以select任何东西) 也就是它只在乎括号里的数据能不能查找出来,是否存在这样的记录。

    题: 查询有商品的栏目.

    按上面的理解,我们用join来操作,如下:

    mysql> select c.cat_id,cat_name from ecs_category as c inner join goods as g
    
    on c.cat_id=g.cat_id group by cat_name;
    
     

    exists简单用法:

    1.  查询id为5的数据:  (数据存在)

    复制代码
        SELECT *
        FROM class AS c1
        WHERE EXISTS(SELECT
                   class_id
                 FROM class AS c2
                 WHERE c1.class_id = 5);
    复制代码

      如果exists里面返回的结果行数大于1,则返回true,则外面的查询数据可以返回。

     2.  查询id为10的数据:  (数据不存在)

    复制代码
        SELECT *
        FROM class AS c1
        WHERE EXISTS(SELECT
                   class_id
                 FROM class AS c2
                 WHERE c1.class_id = 10);
    复制代码

       因为exsits始终返回的是false,所以外层查询始终无效,也就不会产生数据。

    自己的实践:in  用到了索引,exists没用到索引。。。。。。。。。。。。。

    mysql> explain select id,name from user as u where exists(select class_id from c
    lass as c where c.class_id=u.class_id and c.class_name='3')G
    *************************** 1. row ***************************
               id: 1
      select_type: PRIMARY
            table: u
       partitions: NULL
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 1116336
         filtered: 100.00
            Extra: Using where
    *************************** 2. row ***************************
               id: 2
      select_type: DEPENDENT SUBQUERY
            table: c
       partitions: NULL
             type: eq_ref
    possible_keys: PRIMARY
              key: PRIMARY
          key_len: 4
              ref: maven.u.class_id
             rows: 1
         filtered: 20.00
            Extra: Using where
    2 rows in set, 2 warnings (0.00 sec)
    
    mysql> explain select id,name from user as u where class_id in(select class_id f
    rom class as c where c.class_id=u.class_id and c.class_name='3')G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: c
       partitions: NULL
             type: ALL
    possible_keys: PRIMARY
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 5
         filtered: 20.00
            Extra: Using where
    *************************** 2. row ***************************
               id: 1
      select_type: SIMPLE
            table: u
       partitions: NULL
             type: ref
    possible_keys: class_id
              key: class_id
          key_len: 5
              ref: maven.c.class_id
             rows: 1
         filtered: 100.00
            Extra: NULL
    2 rows in set, 2 warnings (0.00 sec)

    3.优化规则:

    优化1: 在group时, 用带有索引的列来group, 速度会稍快一些,另外,int型 比 char型 分组,也要快一些.

     

     

    优化2: 在group时, 我们假设只取了A表的内容,group by 的列,尽量用A表的列,会比B表的列要快.

     

    优化3: 从语义上去优化

      select cat_id,cat_name from ecs_category where exists(select *from goods where goods.cat_id=ecs_category.cat_id)

     

    优化4:from 型子查询:

      注意::内层from语句查到的临时表, 是没有索引的.所以: from的返回内容要尽量少.

     

    优化5:min()   max()优化

      例如:如下表结构:

     

    mysql> show create table userG
    *************************** 1. row ***************************
           Table: user
    Create Table: CREATE TABLE `user` (
      `id` int(11) NOT NULL,
      `name` varchar(22) DEFAULT NULL,
      `class_id` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8
    1 row in set (0.00 sec)

     

    mysql> select count(id) from user;
    +-----------+
    | count(id) |
    +-----------+
    |   1120013 |
    +-----------+
    1 row in set (2.32 sec)

      现在查询班级编号为5的最小的id:

    mysql> select min(id) from user where class_id=5;
    +---------+
    | min(id) |
    +---------+
    |       5 |
    +---------+
    1 row in set (2.37 sec)
    
    mysql> explain select min(id) from user where class_id=5G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: user
       partitions: NULL
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 1116336
         filtered: 10.00
            Extra: Using where
    1 row in set, 1 warning (0.00 sec)

     

      分析之后是全表扫描,需要扫描11W多行,因为class_id没有索引。

     

    优化思路:

      (1)在class_id上加上索引可以加快查询思路

      (2)在不加索引的情况下优化:

        

        试想 id是有顺序的,(默认索引是升续排列), 因此,如果我们沿着id的索引方向走,那么 第1个 pclass_id=5的索引结点,他的id就正好是最小的id.

     

     

    mysql> explain select min(id) from user where class_id=5G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: user
       partitions: NULL
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 1116336
         filtered: 10.00
            Extra: Using where
    1 row in set, 1 warning (0.00 sec)
    
    mysql> select id from user use index(primary) where class_id=5 limit 1;
    +----+
    | id |
    +----+
    |  5 |
    +----+
    1 row in set (0.10 sec)
    
    mysql> explain select id from user use index(primary) where class_id=5 limit 1G
    
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: user
       partitions: NULL
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 1116336
         filtered: 10.00
            Extra: Using where
    1 row in set, 1 warning (0.00 sec)

     

      虽然说也没用到索引,可是扫描会在扫描到第一个就停下来,因此扫描的实际数据比上面少,因此查询速度快。

     

      总结:min,max中如果where条件没有索引可以使查询沿着有索引的方向走,找到第一个数据后停下来即满足条件。

     

    优化6:  count()优化

      

    1:myisam的count()非常快

    答: 是比较快,.但仅限于查询表的所有行比较快, 因为Myisam对行数进行了存储.一旦有条件的查询, 速度就不再快了.尤其是where条件的列上没有索引.

      

    2: 假如,id<100的商家都是我们内部测试的,我们想查查真实的商家有多少?

    select count(*) from lx_com where id>=100; (1000多万行用了6.X秒)
    
    小技巧:
    
    select count(*) from lx_com; 快
    
    select count(*) from lx_com where id<100; 快
    
    select count(*) frol lx_com -select count(*) from lx_com where id<100; 快
    
    select (select count(*) from lx_com) - (select count(*) from lx_com where id<100)

     优化7:巧用变量

      1:用变量排名

    表结构如下:

    mysql> show create table testG
    *************************** 1. row ***************************
           Table: test
    Create Table: CREATE TABLE `test` (
      `name` varchar(5) DEFAULT NULL,
      `score` int(11) DEFAULT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8
    1 row in set (0.00 sec)

    表数据:

    mysql> select * from test;
    +------+-------+
    | name | score |
    +------+-------+
    | qlq1 |   100 |
    | qlq2 |   100 |
    | qlq3 |    90 |
    | qlq4 |    91 |
    | ql5q |    91 |
    +------+-------+
    5 rows in set (0.00 sec)

    打印名次。利用变量计算。

    mysql> set @curr_cnt := 0,@prev_cnt := 0, @rank := 0;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select name,(@curr_cnt:=score) as score,@rank=if(@curr_cnt<>@prev_cnt,@ra
    nk+1,@rank) as rank,@prev_cnt:=score from test order by score desc;
    +------+-------+------+------------------+
    | name | score | rank | @prev_cnt:=score |
    +------+-------+------+------------------+
    | qlq1 |   100 |    0 |              100 |
    | qlq2 |   100 |    1 |              100 |
    | qlq4 |    91 |    0 |               91 |
    | ql5q |    91 |    1 |               91 |
    | qlq3 |    90 |    0 |               90 |
    +------+-------+------+------------------+
    5 rows in set (0.00 sec)

      

      2.用变量计算真正影响的行数:

    表结构:(name是主键)

    mysql> show create table testG
    *************************** 1. row ***************************
           Table: test
    Create Table: CREATE TABLE `test` (
      `name` varchar(5) DEFAULT NULL,
      `score` int(11) DEFAULT NULL,
      UNIQUE KEY `name` (`name`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8
    1 row in set (0.00 sec)

    这时候我们插入一个重复的主键,主键重复之后修改值,将分数减一:

    mysql> insert into test values('qlq1',99) on DUPLICATE KEY update score=score-1;
    
    Query OK, 2 rows affected (0.15 sec)
    mysql> insert into test values('qlq1',99),('qlq2','5') on DUPLICATE KEY update s
    core=score-1;
    Query OK, 4 rows affected (0.13 sec)
    Records: 2  Duplicates: 2  Warnings: 0

     上面显示影响的是两行,因为增、修改,但实际修改的是一行。这时候如果我们想知道实际修改的行数:(如下)

    mysql> set @x:=0;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> insert into test values('qlq1',99),('qlq2','5') on DUPLICATE KEY update s
    core=score-1+(@x:=@x+1)*0;
    Query OK, 4 rows affected (0.15 sec)
    Records: 2  Duplicates: 2  Warnings: 0
    
    mysql> select @x;
    +------+
    | @x   |
    +------+
    |    2 |
    +------+
    1 row in set (0.00 sec)

       通过变量可以标识实际影响的行数为2行。

     3.简化union

     例如两个表数据:

    mysql> select * from test;
    +------+-------+
    | name | score |
    +------+-------+
    | qlq1 |    97 |
    | qlq2 |    98 |
    | qlq3 |    90 |
    | qlq4 |    91 |
    | ql5q |    91 |
    +------+-------+
    5 rows in set (0.00 sec)
    
    mysql> select * from test2;
    +------+-------+
    | name | score |
    +------+-------+
    | qlq1 |   100 |
    | qlq5 |   100 |
    | qlq3 |    90 |
    +------+-------+
    3 rows in set (0.00 sec)

    采用union取不重复的数据:(all不会去重)

    mysql> select * from test union select * from test2;
    +------+-------+
    | name | score |
    +------+-------+
    | qlq1 |    97 |
    | qlq2 |    98 |
    | qlq3 |    90 |
    | qlq4 |    91 |
    | ql5q |    91 |
    | qlq1 |   100 |
    | qlq5 |   100 |
    +------+-------+
    7 rows in set (0.00 sec)

     explain分析上面语句:

    mysql> explain select * from test union select * from test2G
    *************************** 1. row ***************************
               id: 1
      select_type: PRIMARY
            table: test
       partitions: NULL
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 5
         filtered: 100.00
            Extra: NULL
    *************************** 2. row ***************************
               id: 2
      select_type: UNION
            table: test2
       partitions: NULL
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 3
         filtered: 100.00
            Extra: NULL
    *************************** 3. row ***************************
               id: NULL
      select_type: UNION RESULT
            table: <union1,2>
       partitions: NULL
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: NULL
         filtered: NULL
            Extra: Using temporary
    3 rows in set, 1 warning (0.01 sec)

    如何利用变量让后半句select不执行 ,也就是只查询一个表,两个表都有数据但是我们想让他取一个表;

    set @find:=0;
    
    select name,score,(@find := 1) from test where name='qlq2'
    
    union
    
    select name,score,(@find := 1) from test2 where name='qlq2'  and (@find <= 0)
    
    union 
    
    select 1,1,@find:=0 from test where 0;

      解释:先让find变量置为0,如果name=qlq2在test表找到数据,name会将find置为1,此时不会进入第二个select;如果第一个没有查到,会走到第二个select并查询,查到之后将find置为1,第三个select纯粹是为了将find置为0.

    结果:

    +------+-------+--------------+
    | name | score | (@find := 1) |
    +------+-------+--------------+
    | qlq2 |    98 |            1 |
    +------+-------+--------------+
    1 row in set (0.00 sec)
  • 相关阅读:
    将图片部署在tomcat/iportWork/uploadFiles中
    idea中的svn配置
    mavan和idea的搭建,很好的一篇文章
    C#如何遍历数组?
    java 泛型小小的测试题
    js关于变量作为if条件的真假问题
    HTML5实现两个视频循环播放!
    JQuery利用选择器定位动态id?
    hibernate QBC查询
    mybatis前台传来一个String,后后台执行sql变成了true
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/8595168.html
Copyright © 2011-2022 走看看