文章目录

  • 一、索引的使用
  • 1. 环境准备
  • 2. 避免索引失效
  • 3. 查看索引使用情况
  • 二、SQL优化
  • 1. 大批量插入数据
  • 2. 优化insert语句
  • 3. 优化order by语句
  • 4. 优化group by 语句
  • 5. 优化嵌套查询
  • 6. 优化OR条件
  • 7. 优化分页查询


一、索引的使用

索引是数据库优化最常用也是最重要的手段之一, 通过索引通常可以帮助用户解决大多数的MySQL的性能优化问题。

1. 环境准备

sqlserver查询索引优化 sql优化,以及索引的使用_mysql


创建复合索引:

create index idx_seller_name_sta_addr on tb_seller(name,status,address);
2. 避免索引失效

1). 全值匹配 ,对索引中所有列都指定具体值。

该情况下,索引生效,执行效率高。

mysql> explain select * from tb_seller where name='小米科技' and status='1' and address='北京市'\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb_seller
         type: ref
possible_keys: idx_seller_name_sta_addr
          key: idx_seller_name_sta_addr
      key_len: 813
          ref: const,const,const
         rows: 1
        Extra: Using index condition
1 row in set (0.02 sec)

2). 最左前缀法则

如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始,并且不跳过索引中的列。

匹配最左前缀法则,走索引:

create index idx_seller_name_sta_addr on tb_seller(name,status,address);

这个索引时复合索引,要遵守最左前缀法则:

<!--走索引-->
mysql> explain select * from tb_seller where name='小米科技'\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb_seller
         type: ref
possible_keys: idx_seller_name_sta_addr
          key: idx_seller_name_sta_addr
      key_len: 403
          ref: const
         rows: 1
        Extra: Using index condition
1 row in set (0.01 sec)

<!--走索引-->
mysql> explain select * from tb_seller where name='小米科技' and status='1'\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb_seller
         type: ref
possible_keys: idx_seller_name_sta_addr
          key: idx_seller_name_sta_addr
      key_len: 410
          ref: const,const
         rows: 1
        Extra: Using index condition

<!--没有遵循最左索引法则不走索引-->
mysql> explain select * from tb_seller where status='1' and address='北京市'\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb_seller
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 12
        Extra: Using where

<!--查询条件中包含最左列并且没有跳跃某一列的话就走索引-->
mysql> explain select * from tb_seller where status='1' and address='北京市' and name='小米科技'\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb_seller
         type: ref
possible_keys: idx_seller_name_sta_addr
          key: idx_seller_name_sta_addr
      key_len: 813
          ref: const,const,const
         rows: 1
        Extra: Using index condition
        
<!--如果符合最左法则,但是出现跳跃某一列,只有最左列索引生效:-->
mysql> explain select * from tb_seller where  name='小米科技' and address='北京市'\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb_seller
         type: ref
possible_keys: idx_seller_name_sta_addr
          key: idx_seller_name_sta_addr
      key_len: 403
          ref: const
         rows: 1
        Extra: Using index condition
1 row in set (0.01 sec)

3). 范围查询右边的列,不能使用索引

<!--字段name , status 查询是走索引的, 但是最后一个条件address 没有用到索引-->
mysql> explain select * from tb_seller where name='小米科技' and status>'1' and address='北京市'\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb_seller
         type: range
possible_keys: idx_seller_name_sta_addr
          key: idx_seller_name_sta_addr
      key_len: 410
          ref: NULL
         rows: 1
        Extra: Using index condition
1 row in set (0.00 sec)

4). 不要在索引列上进行运算操作, 索引将失效。

mysql> select * from tb_seller where substring(name,3,2) = '科技';
+----------+--------------------------------+-----------------------+----------------------------------+--------+-----------+---------------------+
| sellerid | name                           | nickname              | password                         | status | address   | createtime          |
+----------+--------------------------------+-----------------------+----------------------------------+--------+-----------+---------------------+
| baidu    | 百度科技有限公司               | 百度小店              | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| huawei   | 华为科技有限公司               | 华为小店              | e10adc3949ba59abbe56e057f20f883e | 0      | 北京市    | 2088-01-01 12:00:00 |
| luoji    | 罗技科技有限公司               | 罗技小店              | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| ourpalm  | 掌趣科技股份有限公司           | 掌趣小店              | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| qiandu   | 千度科技                       | 千度小店              | e10adc3949ba59abbe56e057f20f883e | 2      | 北京市    | 2088-01-01 12:00:00 |
| sina     | 新浪科技有限公司               | 新浪官方旗舰店        | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| xiaomi   | 小米科技                       | 小米官方旗舰店        | e10adc3949ba59abbe56e057f20f883e | 1      | 西安市    | 2088-01-01 12:00:00 |
+----------+--------------------------------+-----------------------+----------------------------------+--------+-----------+---------------------+
7 rows in set (0.00 sec)

mysql> explain select * from tb_seller where substring(name,3,2) = '科技';
+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table     | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | tb_seller | ALL  | NULL          | NULL | NULL    | NULL |   12 | Using where |
+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+

5). 字符串不加单引号,造成索引失效。

由于,在查询时,没有对字符串加单引号,MySQL的查询优化器,会自动的进行类型转换,造成索引失效

<!--status没有走索引-->
mysql> explain select * from tb_seller where name='小米科技' and status=1\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb_seller
         type: ref
possible_keys: idx_seller_name_sta_addr
          key: idx_seller_name_sta_addr
      key_len: 403
          ref: const
         rows: 1
        Extra: Using index condition

<!--status走了索引-->
mysql> explain select * from tb_seller where name='小米科技' and status='1'\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb_seller
         type: ref
possible_keys: idx_seller_name_sta_addr
          key: idx_seller_name_sta_addr
      key_len: 410
          ref: const,const
         rows: 1
        Extra: Using index condition
1 row in set (0.00 sec)

6). 尽量使用覆盖索引,避免select *

尽量使用覆盖索引(只访问索引的查询(索引列完全包含查询列)),减少select * 。

mysql> explain select name,status,address from tb_seller where name='小米科技'\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb_seller
         type: ref
possible_keys: idx_seller_name_sta_addr
          key: idx_seller_name_sta_addr
      key_len: 403
          ref: const
         rows: 1
        Extra: Using where; Using index
1 row in set (0.00 sec)

<!--Extra发生了变化-->
mysql> explain select * from tb_seller where name='小米科技'\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb_seller
         type: ref
possible_keys: idx_seller_name_sta_addr
          key: idx_seller_name_sta_addr
      key_len: 403
          ref: const
         rows: 1
        Extra: Using index condition
1 row in set (0.00 sec)

TIP :

using index :使用覆盖索引的时候就会出现

using where:在查找使用索引的情况下,需要回表去查询所需的数据

using index condition:查找使用了索引,但是需要回表查询数据

using index ; using where:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据

7). 用or分割开的条件, 如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到。

示例,name字段是索引列 , createtime不是索引列,中间是or进行连接不走索引:

mysql> explain select * from tb_seller where name='黑马程序员' or createtime = '2088-01-01 12:00:00'\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb_seller
         type: ALL
possible_keys: idx_seller_name_sta_addr
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 12
        Extra: Using where
1 row in set (0.00 sec)

8). 以%开头的Like模糊查询,索引失效。

如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效。

mysql> explain select * from tb_seller where name like '科技%'\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb_seller
         type: range
possible_keys: idx_seller_name_sta_addr
          key: idx_seller_name_sta_addr
      key_len: 403
          ref: NULL
         rows: 1
        Extra: Using index condition
1 row in set (0.00 sec)

<!-索引失效-->
mysql> explain select * from tb_seller where name like '%科技'\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb_seller
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 12
        Extra: Using where
1 row in set (0.00 sec)

解决方案 : 通过覆盖索引来解决

mysql> explain select sellerid,name,status from tb_seller where name like '%科技%'\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb_seller
         type: index
possible_keys: NULL
          key: idx_seller_name_sta_addr
      key_len: 813
          ref: NULL
         rows: 12
        Extra: Using where; Using index
1 row in set (0.00 sec)

9). 如果MySQL评估使用索引比全表更慢,则不使用索引。

mysql> explain select * from tb_seller where address='北京市';
+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table     | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | tb_seller | ALL  | NULL          | NULL | NULL    | NULL |   12 | Using where |
+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+

mysql> create index idx_seller_address on tb_seller(address);

mysql> explain select * from tb_seller where address='北京市';
+----+-------------+-----------+------+--------------------+------+---------+------+------+-------------+
| id | select_type | table     | type | possible_keys      | key  | key_len | ref  | rows | Extra       |
+----+-------------+-----------+------+--------------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | tb_seller | ALL  | idx_seller_address | NULL | NULL    | NULL |   12 | Using where |
+----+-------------+-----------+------+--------------------+------+---------+------+------+-------------+

<!--12条数据11条都是北京市,走全表扫描比走索引更快-->
mysql> explain select * from tb_seller where address='西安市';
+----+-------------+-----------+------+--------------------+--------------------+---------+-------+------+-----------------------+
| id | select_type | table     | type | possible_keys      | key                | key_len | ref   | rows | Extra                 |
+----+-------------+-----------+------+--------------------+--------------------+---------+-------+------+-----------------------+
|  1 | SIMPLE      | tb_seller | ref  | idx_seller_address | idx_seller_address | 403     | const |    1 | Using index condition |
+----+-------------+-----------+------+--------------------+--------------------+---------+-------+------+-----------------------+

<!--只有一个数据使用索引-->
mysql> select * from tb_seller;
+----------+--------------------------------------+-----------------------+----------------------------------+--------+-----------+---------------------+
| sellerid | name                                 | nickname              | password                         | status | address   | createtime          |
+----------+--------------------------------------+-----------------------+----------------------------------+--------+-----------+---------------------+
| alibaba  | 阿里巴巴                             | 阿里小店              | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| baidu    | 百度科技有限公司                     | 百度小店              | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| huawei   | 华为科技有限公司                     | 华为小店              | e10adc3949ba59abbe56e057f20f883e | 0      | 北京市    | 2088-01-01 12:00:00 |
| itcast   | 传智播客教育科技有限公司             | 传智播客              | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| itheima  | 黑马程序员                           | 黑马程序员            | e10adc3949ba59abbe56e057f20f883e | 0      | 北京市    | 2088-01-01 12:00:00 |
| luoji    | 罗技科技有限公司                     | 罗技小店              | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| oppo     | OPPO科技有限公司                     | OPPO官方旗舰店        | e10adc3949ba59abbe56e057f20f883e | 0      | 北京市    | 2088-01-01 12:00:00 |
| ourpalm  | 掌趣科技股份有限公司                 | 掌趣小店              | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| qiandu   | 千度科技                             | 千度小店              | e10adc3949ba59abbe56e057f20f883e | 2      | 北京市    | 2088-01-01 12:00:00 |
| sina     | 新浪科技有限公司                     | 新浪官方旗舰店        | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| xiaomi   | 小米科技                             | 小米官方旗舰店        | e10adc3949ba59abbe56e057f20f883e | 1      | 西安市    | 2088-01-01 12:00:00 |
| yijia    | 宜家家居                             | 宜家家居旗舰店        | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
+----------+--------------------------------------+-----------------------+----------------------------------+--------+-----------+---------------------+

10). is NULL , is NOT NULL 有时索引失效。

<!--所有的数据都是not null 因此就不走索引,走全表扫描-->
mysql> explain select * from tb_seller where address is not null\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb_seller
         type: ALL
possible_keys: idx_seller_address
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 12
        Extra: Using where
1 row in set (0.00 sec)

<!--null的数量比较少时就会走索引-->
mysql> explain select * from tb_seller where address is null\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tb_seller
         type: ref
possible_keys: idx_seller_address
          key: idx_seller_address
      key_len: 403
          ref: const
         rows: 1
        Extra: Using index condition
1 row in set (0.00 sec)

12). 单列索引和复合索引。

尽量使用复合索引,而少使用单列索引 。
创建复合索引

create index idx_name_sta_address on tb_seller(name, status, address);

就相当于创建了三个索引 : 
	name
	name + status
	name + status + address

创建单列索引

create index idx_seller_name on tb_seller(name);
create index idx_seller_status on tb_seller(status);
create index idx_seller_address on tb_seller(address);

数据库会选择一个最优的索引(辨识度最高索引)来使用,并不会使用全部索引 。

3. 查看索引使用情况
show status like 'Handler_read%';	
show global status like 'Handler_read%';
mysql> show status like 'Handler_read%';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| Handler_read_first    | 4     |
| Handler_read_key      | 4     |
| Handler_read_last     | 0     |
| Handler_read_next     | 0     |
| Handler_read_prev     | 0     |
| Handler_read_rnd      | 0     |
| Handler_read_rnd_next | 73    |
+-----------------------+-------+
7 rows in set (0.00 sec)

二、SQL优化

1. 大批量插入数据

插入数较多时,如何优化插入数据:

环境准备:

CREATE TABLE `tb_user_1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(45) NOT NULL,
  `password` varchar(96) NOT NULL,
  `name` varchar(45) NOT NULL,
  `birthday` datetime DEFAULT NULL,
  `sex` char(1) DEFAULT NULL,
  `email` varchar(45) DEFAULT NULL,
  `phone` varchar(45) DEFAULT NULL,
  `qq` varchar(32) DEFAULT NULL,
  `status` varchar(32) NOT NULL COMMENT '用户状态',
  `create_time` datetime NOT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_user_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;

sqlserver查询索引优化 sql优化,以及索引的使用_sqlserver查询索引优化_02

当使用load 命令导入数据的时候,适当的设置可以提高导入的效率。

sqlserver查询索引优化 sql优化,以及索引的使用_MySQL_03


对于 InnoDB 类型的表,有以下几种方式可以提高导入的效率:

1) 主键顺序插入

因为InnoDB类型的表是按照主键的顺序保存的,所以将导入的数据按照主键的顺序排列,可以有效的提高导入数据的效率。如果InnoDB表没有主键,那么系统会自动默认创建一个内部列作为主键,所以如果可以给表创建一个主键,将可以利用这点,来提高导入数据的效率。

脚本文件介绍 :

sql1.log  ----> 主键有序
	sql2.log  ----> 主键无序

插入ID顺序排列数据:

mysql> load data local infile '/root/sql1.log' into table `tb_user_1` fields terminated by ',' lines terminated by '\n';
Query OK, 1000000 rows affected, 65535 warnings (2 min 3.40 sec)
Records: 1000000  Deleted: 0  Skipped: 0  Warnings: 4000000

插入ID无序排列数据:

mysql> load data local infile '/root/sql2.log' into table `tb_user_2` fields terminated by ',' lines terminated by '\n';
Query OK, 1000000 rows affected, 65535 warnings (4 min 35.61 sec)
Records: 1000000  Deleted: 0  Skipped: 0  Warnings: 4000000

2) 关闭唯一性校验

在导入数据前执行 SET UNIQUE_CHECKS=0,关闭唯一性校验,在导入结束后执行SET UNIQUE_CHECKS=1,恢复唯一性校验,可以提高导入的效率

3) 手动提交事务

如果应用使用自动提交的方式,建议在导入前执行 SET AUTOCOMMIT=0,关闭自动提交,导入结束后再执行 SET AUTOCOMMIT=1,打开自动提交,也可以提高导入的效率。

2. 优化insert语句

当进行数据的insert操作的时候,可以考虑采用以下几种优化方案。

  • 如果需要同时对一张表插入很多行数据时,应该尽量使用多个值表的insert语句,这种方式将大大的缩减客户端与数据库之间的连接、关闭等消耗。使得效率比分开执行的单个insert语句快。
    示例, 原始方式为:
insert into tb_test values(1,'Tom');
insert into tb_test values(2,'Cat');
insert into tb_test values(3,'Jerry');

优化后的方案为 :

insert into tb_test values(1,'Tom'),(2,'Cat'),(3,'Jerry');
  • 在事务中进行数据插入。
start transaction;
insert into tb_test values(1,'Tom');
insert into tb_test values(2,'Cat');
insert into tb_test values(3,'Jerry');
commit;
  • 数据有序插入
insert into tb_test values(4,'Tim');
insert into tb_test values(1,'Tom');
insert into tb_test values(3,'Jerry');
insert into tb_test values(5,'Rose');
insert into tb_test values(2,'Cat');

优化后

insert into tb_test values(1,'Tom');
insert into tb_test values(2,'Cat');
insert into tb_test values(3,'Jerry');
insert into tb_test values(4,'Tim');
insert into tb_test values(5,'Rose');
3. 优化order by语句

1、环境准备:

sqlserver查询索引优化 sql优化,以及索引的使用_MySQL_04


创建复合索引:

create index idx_emp_age_salary on emp(age,salary);

2、两种排序方式:

1). 第一种是通过对返回数据进行排序,也就是通常说的 filesort 排序,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序

mysql> explain select * from emp order by age\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: emp
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 12
        Extra: Using filesort
1 row in set (0.00 sec)

2). 第二种通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高。

mysql> explain select id from emp order by age\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: emp
         type: index
possible_keys: NULL
          key: idx_emp_age_salary
      key_len: 9
          ref: NULL
         rows: 12
        Extra: Using index
1 row in set (0.00 sec)

多字段排序

mysql> explain select id,age,salary from emp order by age,salary\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: emp
         type: index
possible_keys: NULL
          key: idx_emp_age_salary
      key_len: 9
          ref: NULL
         rows: 12
        Extra: Using index

mysql> explain select age,salary from emp order by salary,age\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: emp
         type: index
possible_keys: NULL
          key: idx_emp_age_salary
      key_len: 9
          ref: NULL
         rows: 12
        Extra: Using index; Using filesort
1 row in set (0.00 sec)

了解了MySQL的排序方式,优化目标就清晰了:尽量减少额外的排序,通过索引直接返回有序数据。where 条件和Order by 使用相同的索引,并且Order By 的顺序和索引顺序相同, 并且Order by 的字段都是升序,或者都是降序。否则肯定需要额外的操作,这样就会出现FileSort。

4. 优化group by 语句

由于GROUP BY 实际上也同样会进行排序操作,而且与ORDER BY 相比,GROUP BY 主要只是多了排序之后的分组操作。当然,如果在分组的时候还使用了其他的一些聚合函数,那么还需要一些聚合函数的计算。所以,在GROUP BY 的实现过程中,与 ORDER BY 一样也可以利用到索引。

如果查询包含 group by 但是用户想要避免排序结果的消耗, 则可以执行order by null 禁止排序。如下 :

mysql> drop index idx_emp_age_salary on emp;

mysql> explain select age,count(*) from emp group by age\G';
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: emp
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 12
        Extra: Using temporary; Using filesort

优化后:

mysql> explain select age,count(*) from emp group by age order by null\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: emp
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 12
        Extra: Using temporary
1 row in set (0.01 sec)

从上面的例子可以看出,第一个SQL语句需要进行"filesort",而第二个SQL由于order by null 不需要进行 “filesort”, 而上文提过Filesort往往非常耗费时间。

可以通过创建索引提高效率:

mysql> create index idx_emp_age_salary on emp(age,salary);

mysql> explain select age,count(*) from emp group by age order by null\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: emp
         type: index
possible_keys: idx_emp_age_salary
          key: idx_emp_age_salary
      key_len: 9
          ref: NULL
         rows: 12
        Extra: Using index
5. 优化嵌套查询

Mysql4.1版本之后,开始支持SQL的子查询。这个技术可以使用SELECT语句来创建一个单列的查询结果,然后把这个结果作为过滤条件用在另一个查询中。使用子查询可以一次性的完成很多逻辑上需要多个步骤才能完成的SQL操作,同时也可以避免事务或者表锁死,并且写起来也很容易。但是,有些情况下,子查询是可以被更高效的连接(JOIN)替代。

示例 ,查找有角色的所有的用户信息 :

mysql> explain select * from t_user where id in (select user_id from user_role );
+----+--------------+-------------+--------+---------------+---------------+---------+-------------------+------+-------------+
| id | select_type  | table       | type   | possible_keys | key           | key_len | ref               | rows | Extra       |
+----+--------------+-------------+--------+---------------+---------------+---------+-------------------+------+-------------+
|  1 | SIMPLE       | t_user      | ALL    | PRIMARY       | NULL          | NULL    | NULL              |    6 | Using where |
|  1 | SIMPLE       | <subquery2> | eq_ref | <auto_key>    | <auto_key>    | 99      | demo_01.t_user.id |    1 | NULL        |
|  2 | MATERIALIZED | user_role   | index  | fk_ur_user_id | fk_ur_user_id | 99      | NULL              |    6 | Using index |
+----+--------------+-------------+--------+---------------+---------------+---------+-------------------+------+-------------+
3 rows in set (0.01 sec)

使用多表查询代替子查询:

mysql> explain select * from t_user u , user_role ur where u.id = ur.user_id;
+----+-------------+-------+------+---------------+---------------+---------+--------------+------+-------+
| id | select_type | table | type | possible_keys | key           | key_len | ref          | rows | Extra |
+----+-------------+-------+------+---------------+---------------+---------+--------------+------+-------+
|  1 | SIMPLE      | u     | ALL  | PRIMARY       | NULL          | NULL    | NULL         |    6 | NULL  |
|  1 | SIMPLE      | ur    | ref  | fk_ur_user_id | fk_ur_user_id | 99      | demo_01.u.id |    1 | NULL  |
+----+-------------+-------+------+---------------+---------------+---------+--------------+------+-------+
2 rows in set (0.00 sec)

连接(Join)查询之所以更有效率一些 ,是因为MySQL不需要在内存中创建临时表来完成这个逻辑上需要两个步骤的查询工作。

6. 优化OR条件

对于包含OR的查询子句,如果要利用索引,则OR之间的每个条件列都必须用到索引 , 而且不能使用到复合索引; 如果没有索引,则应该考虑增加索引。

mysql> explain select * from emp where id=1 or age=30\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: emp
         type: index_merge
possible_keys: PRIMARY,idx_emp_age_salary
          key: idx_emp_age_salary,PRIMARY
      key_len: 4,4
          ref: NULL
         rows: 2
        Extra: Using sort_union(idx_emp_age_salary,PRIMARY); Using where
1 row in set (0.03 sec)

建议使用 union 替换 or :

mysql> explain select * from emp where id=1 union select * from emp where age=20;
+----+--------------+------------+-------+--------------------+--------------------+---------+-------+------+-----------------+
| id | select_type  | table      | type  | possible_keys      | key                | key_len | ref   | rows | Extra           |
+----+--------------+------------+-------+--------------------+--------------------+---------+-------+------+-----------------+
|  1 | PRIMARY      | emp        | const | PRIMARY            | PRIMARY            | 4       | const |    1 | NULL            |
|  2 | UNION        | emp        | ref   | idx_emp_age_salary | idx_emp_age_salary | 4       | const |    1 | NULL            |
| NULL | UNION RESULT | <union1,2> | ALL   | NULL               | NULL               | NULL    | NULL  | NULL | Using temporary |
+----+--------------+------------+-------+--------------------+--------------------+---------+-------+------+-----------------+
3 rows in set (0.01 sec)

我们来比较下重要指标,发现主要差别是 type 和 ref 这两项

type 显示的是访问类型,是较为重要的一个指标,结果值从好到坏依次是:

system > const > eq_ref > ref > fulltext > ref_or_null  > index_merge > unique_subquery > 
index_subquery > range > index > ALL

UNION 语句的 type 值为 ref,OR 语句的 type 值为 range,可以看到这是一个很明显的差距
UNION 语句的 ref 值为 const,OR 语句的 type 值为 null,const 表示是常量值引用,非常快
这两项的差距就说明了 UNION 要优于 OR 。

7. 优化分页查询

一般分页查询时,通过创建覆盖索引能够比较好地提高性能。

(1)优化思路一

在索引上完成排序分页操作,最后根据主键关联回原表查询所需要的其他列内容。

(2)优化思路二

该方案适用于主键自增的表,可以把Limit 查询转换成某个位置的查询 。