mysql优化语句优化

Posted Qiao_Zhi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 user\\G
*************************** 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=5\\G
*************************** 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=5\\G
*************************** 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 1\\G

*************************** 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<MySQL语句优化技巧?

MySQL10条SQL优化语句,让你的MySQL数据库跑得更快!

mysql如何优化以下语句,查询耗时太久了?

mysql优化之SQL语句优化

Mysql表读写索引等操作的sql语句效率优化问题

10条SQL优化语句,让你的MySQL数据库跑得更快!