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 ;
将以上创建数据库的数据表名改成tb_user_2,可以创建数据表tb_user_2。
当使用load 命令导入数据的时候,适当的设置可以提高导入的效率。
导入的数据格式为:每一列之间使用逗号,分隔,每一行代表的是表结构中的一行数据,每一行以换行符结尾。使用load命令就可以将数据导入到表结构中。
对于 InnoDB 类型的表,有以下几种方式可以提高导入的效率:
1) 主键顺序插入
因为InnoDB类型的表是按照主键的顺序保存的,所以将导入的数据按照主键的顺序排列,可以有效的提高导入数据的效率。如果InnoDB表没有主键,那么系统会自动默认创建一个内部列作为主键,所以如果可以给表创建一个主键,将可以利用这点,来提高导入数据的效率。
脚本文件介绍 :
sql1.log ----> 主键有序
sql2.log ----> 主键无序
脚本下载地址:SQL - 100w条insert脚本 - 顺序乱序
文件比较大,我们查看前面几行。
yujie@yujie:~/database$ head sql1.log
1,"username1","ZPAIFXVC","name1","1957-01-23 20:22:02","1","[email protected]","13301787682","SJVGNDTDWE","0","2019-04-20 22:37:15","2019-04-20 22:37:15"
2,"username2","KPTMIQRZ","name2","1965-08-08 21:45:41","1","[email protected]","13306654008","YEMAHDADMX","0","2019-04-20 22:37:15","2019-04-20 22:37:15"
3,"username3","SUIPRGFT","name3","1979-03-13 06:43:25","1","[email protected]","15600263741","YQEUNZDOLZ","0","2019-04-20 22:37:15","2019-04-20 22:37:15"
4,"username4","WRXCPPDW","name4","1966-08-17 22:17:04","1","[email protected]","13600295782","SMHDIFKQNA","0","2019-04-20 22:37:15","2019-04-20 22:37:15"
5,"username5","CHRJYQUT","name5","1980-08-19 19:48:49","1","[email protected]","13807562817","LKRYPEVXQA","0","2019-04-20 22:37:15","2019-04-20 22:37:15"
6,"username6","XSOBMUKH","name6","2012-03-16 23:05:11","1","[email protected]","13104053383","HBBOTZTEWR","0","2019-04-20 22:37:15","2019-04-20 22:37:15"
7,"username7","HIFJXXYT","name7","1950-11-21 18:45:10","1","[email protected]","15100395090","ZHZGNIRGDJ","0","2019-04-20 22:37:15","2019-04-20 22:37:15"
8,"username8","DXRJCXZH","name8","1970-07-21 13:14:15","1","[email protected]","15005342737","IAGVAXGFFJ","0","2019-04-20 22:37:15","2019-04-20 22:37:15"
9,"username9","PHEAJYLH","name9","1976-12-15 20:20:50","1","[email protected]","15102224340","UZIGREFUBT","0","2019-04-20 22:37:15","2019-04-20 22:37:15"
10,"username10","HNUSCHZJ","name10","1974-07-01 23:27:06","1","[email protected]","13601528196","MUOGGHGVPH","0","2019-04-20 22:37:15","2019-04-20 22:37:15"
sql1中的主键是有序的。
yujie@yujie:~/database$ head sql2.log
349748,"username349748","ZPAIFXVC","name349748","1957-01-23 20:22:02","1","[email protected]","13301787682","SJVGNDTDWE","0","2019-04-20 22:37:15","2019-04-20 22:37:15"
381420,"username381420","KPTMIQRZ","name381420","1965-08-08 21:45:41","1","[email protected]","13306654008","YEMAHDADMX","0","2019-04-20 22:37:15","2019-04-20 22:37:15"
141671,"username141671","SUIPRGFT","name141671","1979-03-13 06:43:25","1","[email protected]","15600263741","YQEUNZDOLZ","0","2019-04-20 22:37:15","2019-04-20 22:37:15"
59795,"username59795","WRXCPPDW","name59795","1966-08-17 22:17:04","1","[email protected]","13600295782","SMHDIFKQNA","0","2019-04-20 22:37:15","2019-04-20 22:37:15"
903466,"username903466","CHRJYQUT","name903466","1980-08-19 19:48:49","1","[email protected]","13807562817","LKRYPEVXQA","0","2019-04-20 22:37:15","2019-04-20 22:37:15"
976279,"username976279","XSOBMUKH","name976279","2012-03-16 23:05:11","1","[email protected]","13104053383","HBBOTZTEWR","0","2019-04-20 22:37:15","2019-04-20 22:37:15"
705500,"username705500","HIFJXXYT","name705500","1950-11-21 18:45:10","1","[email protected]","15100395090","ZHZGNIRGDJ","0","2019-04-20 22:37:15","2019-04-20 22:37:15"
757446,"username757446","DXRJCXZH","name757446","1970-07-21 13:14:15","1","[email protected]","15005342737","IAGVAXGFFJ","0","2019-04-20 22:37:15","2019-04-20 22:37:15"
996314,"username996314","PHEAJYLH","name996314","1976-12-15 20:20:50","1","[email protected]","15102224340","UZIGREFUBT","0","2019-04-20 22:37:15","2019-04-20 22:37:15"
774488,"username774488","HNUSCHZJ","name774488","1974-07-01 23:27:06","1","[email protected]","13601528196","MUOGGHGVPH","0","2019-04-20 22:37:15","2019-04-20 22:37:15"
插入ID顺序排列数据:
插入ID无序排列数据:
可以看到,有序的数据是19s,无序的数据是1min59s。
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');
上面的SQL语句需要连接数据库3次,下面仅需1次
-
在事务中进行数据插入。即事务的提交方式改为手动提交。
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语句
3.1 环境准备
CREATE TABLE `emp` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`age` int(3) NOT NULL,
`salary` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
insert into `emp` (`id`, `name`, `age`, `salary`) values('1','Tom','25','2300');
insert into `emp` (`id`, `name`, `age`, `salary`) values('2','Jerry','30','3500');
insert into `emp` (`id`, `name`, `age`, `salary`) values('3','Luci','25','2800');
insert into `emp` (`id`, `name`, `age`, `salary`) values('4','Jay','36','3500');
insert into `emp` (`id`, `name`, `age`, `salary`) values('5','Tom2','21','2200');
insert into `emp` (`id`, `name`, `age`, `salary`) values('6','Jerry2','31','3300');
insert into `emp` (`id`, `name`, `age`, `salary`) values('7','Luci2','26','2700');
insert into `emp` (`id`, `name`, `age`, `salary`) values('8','Jay2','33','3500');
insert into `emp` (`id`, `name`, `age`, `salary`) values('9','Tom3','23','2400');
insert into `emp` (`id`, `name`, `age`, `salary`) values('10','Jerry3','32','3100');
insert into `emp` (`id`, `name`, `age`, `salary`) values('11','Luci3','26','2900');
insert into `emp` (`id`, `name`, `age`, `salary`) values('12','Jay3','37','4500');
create index idx_emp_age_salary on emp(age,salary);
注意:最后的复合索引idx_emp_age_salary作用在两个字段上,age和salary
mysql> show index from emp;
+-------+------------+--------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+--------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| emp | 0 | PRIMARY | 1 | id | A | 12 | NULL | NULL | | BTREE | | |
| emp | 1 | idx_emp_age_salary | 1 | age | A | 10 | NULL | NULL | | BTREE | | |
| emp | 1 | idx_emp_age_salary | 2 | salary | A | 12 | NULL | NULL | YES | BTREE | | |
+-------+------------+--------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
3 rows in set (0.00 sec)
3.2 两种排序方式
1). 第一种是通过对返回数据进行排序,也就是通常说的 filesort 排序,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序。
mysql> explain select * from emp order by age;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | SIMPLE | emp | NULL | ALL | NULL | NULL | NULL | NULL | 12 | 100.00 | Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
1 row in set, 1 warning (0.01 sec)
mysql> explain select * from emp order by age,salary;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | SIMPLE | emp | NULL | ALL | NULL | NULL | NULL | NULL | 12 | 100.00 | Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
1 row in set, 1 warning (0.00 sec)
我们查询所有的字段。可以看出extra中的值都是using filesort
2). 第二种通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高。
mysql> explain select id from emp order by age,salary;
+----+-------------+-------+------------+-------+---------------+--------------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+--------------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | emp | NULL | index | NULL | idx_emp_age_salary | 9 | NULL | 12 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+--------------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select age,salary from emp order by age,salary;
+----+-------------+-------+------------+-------+---------------+--------------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+--------------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | emp | NULL | index | NULL | idx_emp_age_salary | 9 | NULL | 12 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+--------------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select age,salary,name from emp order by age,salary;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | SIMPLE | emp | NULL | ALL | NULL | NULL | NULL | NULL | 12 | 100.00 | Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
1 row in set, 1 warning (0.00 sec)
我们要查询的字段都是索引时,那么我们就可以使用索引,这就是覆盖索引的问题。如果查询的字段中包含有非索引字段,那么也无法使用索引。
多字段排序
mysql> explain select age,salary from emp order by age,salary;
+----+-------------+-------+------------+-------+---------------+--------------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+--------------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | emp | NULL | index | NULL | idx_emp_age_salary | 9 | NULL | 12 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+--------------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select age,salary from emp order by age asc,salary desc;
+----+-------------+-------+------------+-------+---------------+--------------------+---------+------+------+----------+-----------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+--------------------+---------+------+------+----------+-----------------------------+
| 1 | SIMPLE | emp | NULL | index | NULL | idx_emp_age_salary | 9 | NULL | 12 | 100.00 | Using index; Using filesort |
+----+-------------+-------+------------+-------+---------------+--------------------+---------+------+------+----------+-----------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select age,salary from emp order by salary asc,age asc;
+----+-------------+-------+------------+-------+---------------+--------------------+---------+------+------+----------+-----------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+--------------------+---------+------+------+----------+-----------------------------+
| 1 | SIMPLE | emp | NULL | index | NULL | idx_emp_age_salary | 9 | NULL | 12 | 100.00 | Using index; Using filesort |
+----+-------------+-------+------------+-------+---------------+--------------------+---------+------+------+----------+-----------------------------+
1 row in set, 1 warning (0.00 sec)
多字段排序,如果全部升序或者降序,那么就可以使用索引排序,如果一个升序一个降序,那么不会使用索引排序。另外order by后面排序的顺序要与索引定义字段的顺序一致,如最后一个例子中,索引定义时,age在前,salary在后,而我们order by后面,salary在前,age在后,最后出现了using filesort。
了解了MySQL的排序方式,优化目标就清晰了:尽量减少额外的排序,通过索引直接返回有序数据。where 条件和Order by 使用相同的索引,并且Order By 的顺序和索引顺序相同, 并且Order by 的字段都是升序,或者都是降序。否则肯定需要额外的操作,这样就会出现FileSort。
3.3 Filesort 的优化
通过创建合适的索引,能够减少 Filesort 的出现,但是在某些情况下,条件限制不能让Filesort消失,那就需要加快 Filesort的排序操作。对于Filesort , MySQL 有两种排序算法:
1) 两次扫描算法 :MySQL4.1 之前,使用该方式排序。首先根据条件取出排序字段和行指针信息,然后在排序区 sort buffer 中排序,如果sort buffer不够,则在临时表 temporary table 中存储排序结果。完成排序之后,再根据行指针回表读取记录,该操作可能会导致大量随机I/O操作。
2)一次扫描算法:一次性取出满足条件的所有字段,然后在排序区 sort buffer 中排序后直接输出结果集。排序时内存开销较大,但是排序效率比两次扫描算法要高。
MySQL 通过比较系统变量 max_length_for_sort_data 的大小和Query语句取出的字段总大小, 来判定是否那种排序算法,如果max_length_for_sort_data 更大,那么使用第二种优化之后的算法;否则使用第一种。
可以适当提高 sort_buffer_size 和 max_length_for_sort_data 系统变量,来增大排序区的大小,提高排序的效率。
4 优化group by 语句
由于GROUP BY 实际上也同样会进行排序操作,而且与ORDER BY 相比,**GROUP BY 主要只是多了排序之后的分组操作。**当然,如果在分组的时候还使用了其他的一些聚合函数,那么还需要一些聚合函数的计算。所以,在GROUP BY 的实现过程中,与 ORDER BY 一样也可以利用到索引。
我们先将emp表中的复合索引删去,然后再查看groupby语句的执行计划
mysql> drop index idx_emp_age_salary on emp;
Query OK, 0 rows affected (0.05 sec)
mysql> show index from emp;
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| emp | 0 | PRIMARY | 1 | id | A | 12 | NULL | NULL | | BTREE | | |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
1 row in set (0.01 sec)
mysql> select age,count(*) from emp group by age;
+-----+----------+
| age | count(*) |
+-----+----------+
| 21 | 1 |
| 23 | 1 |
| 25 | 2 |
| 26 | 2 |
| 30 | 1 |
| 31 | 1 |
| 32 | 1 |
| 33 | 1 |
| 36 | 1 |
| 37 | 1 |
+-----+----------+
10 rows in set (0.00 sec)
mysql> explain select age,count(*) from emp group by age;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
| 1 | SIMPLE | emp | NULL | ALL | NULL | NULL | NULL | NULL | 12 | 100.00 | Using temporary; Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
1 row in set, 1 warning (0.00 sec)
可以看到,extra中的值为using temporary和using filesort。两者都是比较耗时的操作。而且确实进行了文件的排序
如果查询包含 group by 但是用户想要避免排序结果的消耗, 则可以执行order by null 禁止排序。如下 :
mysql> explain select age,count(*) from emp group by age order by null;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| 1 | SIMPLE | emp | NULL | ALL | NULL | NULL | NULL | NULL | 12 | 100.00 | Using temporary |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+
1 row in set, 1 warning (0.00 sec)
从上面的例子可以看出,第一个SQL语句需要进行"filesort",而第二个SQL由于order by null 不需要进行 “filesort”, 而上文提过Filesort往往非常耗费时间。
此时,filesort消失了,即没有进行文件的排序。但是这里仍然还是有一个using temporary。效率并不高,如何进行优化?
创建索引 :
mysql> create index idx_emp_age_salary on emp(age,salary);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> explain select age,count(*) from emp group by age order by null;
+----+-------------+-------+------------+-------+--------------------+--------------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+--------------------+--------------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | emp | NULL | index | idx_emp_age_salary | idx_emp_age_salary | 9 | NULL | 12 | 100.00 | Using index |
+----+-------------+-------+------------+-------+--------------------+--------------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
5 优化嵌套查询
Mysql4.1版本之后,开始支持SQL的子查询。这个技术可以使用SELECT语句来创建一个单列的查询结果,然后把这个结果作为过滤条件用在另一个查询中。**使用子查询可以一次性的完成很多逻辑上需要多个步骤才能完成的SQL操作,同时也可以避免事务或者表锁死,并且写起来也很容易。**但是,有些情况下,子查询是可以被更高效的连接(JOIN)替代。
尽量多使用多表连接查询。来替换子查询
示例 ,查找有角色的所有的用户信息 :
这里,我们数据库中有三张表t_rol ,t_user ,user_role 三张表,具有关联关系。表结构如图
具体建表语句参见优化SQL步骤,
下面的子查询是通过从user_role表中查询user_id然后返回,从t_user中查询用户信息。
mysql> select * from t_user where id in (select user_id from user_role);
+----+----------+--------------------------------------------------------------+-----------------+
| id | username | password | name |
+----+----------+--------------------------------------------------------------+-----------------+
| 1 | super | $2a$10$TJ4TmCdK.X4wv/tCqHW14.w70U3CC33CeVncD3SLmyMXMknstqKRe | 超级管理员 |
| 2 | admin | $2a$10$TJ4TmCdK.X4wv/tCqHW14.w70U3CC33CeVncD3SLmyMXMknstqKRe | 系统管理员 |
| 3 | itcast | $2a$10$8qmaHgUFUAmPR5pOuWhYWOr291WJYjHelUlYn07k5ELF8ZCrW0Cui | test02 |
| 4 | stu1 | $2a$10$pLtt2KDAFpwTWLjNsmTEi.oU1yOZyIn9XkziK/y/spH5rftCpUMZa | 学生1 |
| 5 | stu2 | $2a$10$nxPKkYSez7uz2YQYUnwhR.z57km3yqKn3Hr/p1FR6ZKgc18u.Tvqm | 学生2 |
+----+----------+--------------------------------------------------------------+-----------------+
5 rows in set (0.01 sec)
mysql> explain select * from t_user where id in (select user_id from user_role);
+----+--------------+-------------+------------+--------+---------------+---------------+---------+-------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+-------------+------------+--------+---------------+---------------+---------+-------------------+------+----------+-------------+
| 1 | SIMPLE | t_user | NULL | ALL | PRIMARY | NULL | NULL | NULL | 6 | 100.00 | Using where |
| 1 | SIMPLE | <subquery2> | NULL | eq_ref | <auto_key> | <auto_key> | 99 | demo_02.t_user.id | 1 | 100.00 | NULL |
| 2 | MATERIALIZED | user_role | NULL | index | fk_ur_user_id | fk_ur_user_id | 99 | NULL | 6 | 100.00 | Using index |
+----+--------------+-------------+------------+--------+---------------+---------------+---------+-------------------+------+----------+-------------+
3 rows in set, 1 warning (0.00 sec)
我们可以看到,type分别为all,eq_ref和index.如果我们用多表连接查询的话。
mysql> explain select * from t_user u,user_role ur where u.id=ur.user_id;
+----+-------------+-------+------------+--------+---------------+---------+---------+--------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------+---------+---------+--------------------+------+----------+-------------+
| 1 | SIMPLE | ur | NULL | ALL | fk_ur_user_id | NULL | NULL | NULL | 6 | 100.00 | Using where |
| 1 | SIMPLE | u | NULL | eq_ref | PRIMARY | PRIMARY | 98 | demo_02.ur.user_id | 1 | 100.00 | NULL |
+----+-------------+-------+------------+--------+---------------+---------+---------+--------------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
我们可以看到,这里type分别为eq_ref,它的效率比index效率高。所以推荐使用多表联合查询替换子查询。
连接(Join)查询之所以更有效率一些 ,是因为MySQL不需要在内存中创建临时表来完成这个逻辑上需要两个步骤的查询工作。
6 优化OR条件
对于包含OR的查询子句,如果要利用索引,则OR之间的每个条件列都必须用到索引 , 而且不能使用到复合索引; 如果没有索引,则应该考虑增加索引。
获取 emp 表中的所有的索引 :
mysql> show index from emp;
+-------+------------+--------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+--------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| emp | 0 | PRIMARY | 1 | id | A | 12 | NULL | NULL | | BTREE | | |
| emp | 1 | idx_emp_age_salary | 1 | age | A | 10 | NULL | NULL | | BTREE | | |
| emp | 1 | idx_emp_age_salary | 2 | salary | A | 12 | NULL | NULL | YES | BTREE | | |
+-------+------------+--------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
3 rows in set (0.00 sec)
这里有主键索引id和复合索引(age和salary)
mysql> explain select * from emp where id=1 or name='Tom';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | emp | NULL | ALL | PRIMARY | NULL | NULL | NULL | 12 | 17.50 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.10 sec)
这里name不是索引列,所以索引失效。
mysql> explain select * from emp where age=20 or salary=3500;
+----+-------------+-------+------------+------+--------------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+--------------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | emp | NULL | ALL | idx_emp_age_salary | NULL | NULL | NULL | 12 | 19.00 | Using where |
+----+-------------+-------+------------+------+--------------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
复合索引也会失效。
建议使用 union 替换 or :
mysql> explain select * from emp where id=1 or id=10;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | emp | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 2 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.01 sec)
mysql> explain select * from emp where id=1 union select * from emp where id=10;
+----+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
| 1 | PRIMARY | emp | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| 2 | UNION | emp | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
3 rows in set, 1 warning (0.01 sec)
可以看到type类型为const时,其效率远高于range
mysql> explain select * from emp where id=1 or id=10;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | emp | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 2 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.01 sec)
mysql> explain select * from emp where id=1 union select * from emp where age=20;
+----+--------------+------------+------------+-------+--------------------+--------------------+---------+-------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+-------+--------------------+--------------------+---------+-------+------+----------+-----------------+
| 1 | PRIMARY | emp | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| 2 | UNION | emp | NULL | ref | idx_emp_age_salary | idx_emp_age_salary | 4 | const | 1 | 100.00 | NULL |
| NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+------------+------------+-------+--------------------+--------------------+---------+-------+------+----------+-----------------+
3 rows in set, 1 warning (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 优化分页查询
一般分页查询时,通过创建覆盖索引能够比较好地提高性能。一个常见又非常头疼的问题就是 limit 2000000,10 ,此时需要MySQL排序前2000010 记录,仅仅返回2000000 - 2000010 的记录,其他记录丢弃,查询排序的代价非常大 。
mysql> select count(*) from tb_item;
+----------+
| count(*) |
+----------+
| 2499695 |
+----------+
1 row in set (7.49 sec)
mysql> select * from tb_item limit 2,10;
+----+-------------+----------+-------+------------+--------+------------+---------------------+---------------------+
| id | title | price | num | categoryid | status | sellerid | createtime | updatetime |
+----+-------------+----------+-------+------------+--------+------------+---------------------+---------------------+
| 3 | 货物3号 | 45056.68 | 14941 | 3 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 4 | 货物4号 | 52856.07 | 45672 | 6 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 5 | 货物5号 | 11955.43 | 50393 | 1 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 6 | 货物6号 | 29320.78 | 98303 | 0 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 7 | 货物7号 | 22865.75 | 3662 | 4 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 8 | 货物8号 | 37598.81 | 38841 | 8 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 9 | 货物9号 | 90529.63 | 8415 | 7 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 10 | 货物10号 | 27202.53 | 24542 | 4 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 11 | 货物11号 | 31902.64 | 36194 | 8 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 12 | 货物12号 | 17742.85 | 32918 | 1 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
+----+-------------+----------+-------+------------+--------+------------+---------------------+---------------------+
10 rows in set (0.00 sec)
mysql> select * from tb_item limit 2000000,10;
+---------+------------------+----------+-------+------------+--------+------------+---------------------+---------------------+
| id | title | price | num | categoryid | status | sellerid | createtime | updatetime |
+---------+------------------+----------+-------+------------+--------+------------+---------------------+---------------------+
| 2000001 | 货物2000001号 | 83001.66 | 39520 | 4 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 2000002 | 货物2000002号 | 24422.86 | 76323 | 0 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 2000003 | 货物2000003号 | 12784.41 | 38868 | 5 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 2000004 | 货物2000004号 | 63336.05 | 48715 | 5 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 2000005 | 货物2000005号 | 21705.14 | 47814 | 7 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 2000006 | 货物2000006号 | 26337.64 | 9819 | 7 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 2000007 | 货物2000007号 | 20973.03 | 94605 | 1 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 2000008 | 货物2000008号 | 66725.88 | 3303 | 1 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 2000009 | 货物2000009号 | 71798.51 | 9966 | 3 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 2000010 | 货物2000010号 | 42282.26 | 8102 | 1 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
+---------+------------------+----------+-------+------------+--------+------------+---------------------+---------------------+
10 rows in set (7.41 sec)
可以看到,都是查询10条数据,一个是从第3条数据开始,一个是从2000001条数据开始,一个用了0s,一个用了7s。因为 limit 2000000,10 ,此时需要MySQL排序前2000010 记录,仅仅返回2000000 - 2000010 的记录,其他记录丢弃,查询排序的代价非常大 。(普通limit会从前往后遍历数据,所以分页查询的效率会随着偏移量的增加而明显降低。)
7.1 优化思路一
在索引上完成排序分页操作,最后根据主键关联回原表查询所需要的其他列内容。
mysql> select * from tb_item t,(select id from tb_item order by id limit 2000000,10) a where a.id=t.id;
+---------+------------------+----------+-------+------------+--------+------------+---------------------+---------------------+---------+
| id | title | price | num | categoryid | status | sellerid | createtime | updatetime | id |
+---------+------------------+----------+-------+------------+--------+------------+---------------------+---------------------+---------+
| 2000001 | 货物2000001号 | 83001.66 | 39520 | 4 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 | 2000001 |
| 2000002 | 货物2000002号 | 24422.86 | 76323 | 0 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 | 2000002 |
| 2000003 | 货物2000003号 | 12784.41 | 38868 | 5 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 | 2000003 |
| 2000004 | 货物2000004号 | 63336.05 | 48715 | 5 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 | 2000004 |
| 2000005 | 货物2000005号 | 21705.14 | 47814 | 7 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 | 2000005 |
| 2000006 | 货物2000006号 | 26337.64 | 9819 | 7 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 | 2000006 |
| 2000007 | 货物2000007号 | 20973.03 | 94605 | 1 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 | 2000007 |
| 2000008 | 货物2000008号 | 66725.88 | 3303 | 1 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 | 2000008 |
| 2000009 | 货物2000009号 | 71798.51 | 9966 | 3 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 | 2000009 |
| 2000010 | 货物2000010号 | 42282.26 | 8102 | 1 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 | 2000010 |
+---------+------------------+----------+-------+------------+--------+------------+---------------------+---------------------+---------+
10 rows in set (2.08 sec)
分析他们的执行计划
mysql> explain select * from tb_item t,(select id from tb_item order by id limit
2000000,
+----+-------------+------------+------------+--------+---------------+---------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+--------+---------------+---------+---------+------+---------+----------+-------------+
| 1 | PRIMARY | <derived2> | NULL | ALL | NULL | NULL | NULL | NULL | 2000010 | 100.00 | NULL |
| 1 | PRIMARY | t | NULL | eq_ref | PRIMARY | PRIMARY | 4 | a.id | 1 | 100.00 | NULL |
| 2 | DERIVED | tb_item | NULL | index | NULL | PRIMARY | 4 | NULL | 2000010 | 100.00 | Using index |
+----+-------------+------------+------------+--------+---------------+---------+---------+------+---------+----------+-------------+
3 rows in set, 1 warning (0.01 sec)
mysql> explain select * from tb_item limit 2000000,10; +----+-------------+---------+------------+------+---------------+------+---------+------+---------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+---------+----------+-------+
| 1 | SIMPLE | tb_item | NULL | ALL | NULL | NULL | NULL | NULL | 2487696 | 100.00 | NULL |
+----+-------------+---------+------------+------+---------------+------+---------+------+---------+----------+-------+
1 row in set, 1 warning (0.00 sec)
上面的用到了索引,下面的没有用到索引。
7.2 优化思路二
该方案适用于主键自增的表,可以把Limit 查询转换成某个位置的查询 。
因为主键自增,所以我们可以这样做
mysql> select * from tb_item where id>2000000 limit 10;
+---------+------------------+----------+-------+------------+--------+------------+---------------------+---------------------+
| id | title | price | num | categoryid | status | sellerid | createtime | updatetime |
+---------+------------------+----------+-------+------------+--------+------------+---------------------+---------------------+
| 2000001 | 货物2000001号 | 83001.66 | 39520 | 4 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 2000002 | 货物2000002号 | 24422.86 | 76323 | 0 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 2000003 | 货物2000003号 | 12784.41 | 38868 | 5 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 2000004 | 货物2000004号 | 63336.05 | 48715 | 5 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 2000005 | 货物2000005号 | 21705.14 | 47814 | 7 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 2000006 | 货物2000006号 | 26337.64 | 9819 | 7 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 2000007 | 货物2000007号 | 20973.03 | 94605 | 1 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 2000008 | 货物2000008号 | 66725.88 | 3303 | 1 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 2000009 | 货物2000009号 | 71798.51 | 9966 | 3 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
| 2000010 | 货物2000010号 | 42282.26 | 8102 | 1 | 1 | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
+---------+------------------+----------+-------+------------+--------+------------+---------------------+---------------------+
10 rows in set (0.00 sec)
可以看到,这里0s就完成了。效率非常高
这里要求主键自增,而且自增还不能出现断层。比如说在我们插入1,2,3,4,5,6,7,8,9,10之后,我们删除了两个数据,9,10.现在插入的时候,主键的值为11.此时这个操作就不准确了。
8 使用SQL提示
SQL提示,是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。
8.1 USE INDEX
在查询语句中表名的后面,添加 use index 来提供希望MySQL去参考的索引列表,就可以让MySQL不再考虑其他可用的索引。
mysql> explain select * from tb_seller use index(idx_seller_name_sta_addr) wher
e name='小米科技';
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 |
+----------+--------------------------------------+-----------------------+----------------------------------+--------+-----------+---------------------+
12 rows in set (0.05 sec)
mysql> create index idx_seller_name_sta_addr on tb_seller(name,status,address);
Query OK, 0 rows affected (0.27 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show index from tb_seller;
+-----------+------------+--------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-----------+------------+--------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| tb_seller | 0 | PRIMARY | 1 | sellerid | A | 11 | NULL | NULL | | BTREE | | |
| tb_seller | 1 | idx_seller_address | 1 | address | A | 2 | NULL | NULL | YES | BTREE | | |
| tb_seller | 1 | idx_seller_name | 1 | name | A | 12 | NULL | NULL | YES | BTREE | | |
| tb_seller | 1 | idx_seller_status | 1 | status | A | 3 | NULL | NULL | YES | BTREE | | |
| tb_seller | 1 | idx_seller_name_sta_addr | 1 | name | A | 12 | NULL | NULL | YES | BTREE | | |
| tb_seller | 1 | idx_seller_name_sta_addr | 2 | status | A | 12 | NULL | NULL | YES | BTREE | | |
| tb_seller | 1 | idx_seller_name_sta_addr | 3 | address | A | 12 | NULL | NULL | YES | BTREE | | |
+-----------+------------+--------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
7 rows in set (0.00 sec)
我们给name,status,address创建了一个复合索引,同时,在先前我们也对name,status,address创建过了单列索引。可以看到,这里name对应两个索引,一个单列索引,一个复合索引,复合索引符合最左前缀法则。
mysql> explain select * from tb_seller where name='小米科技';
+----+-------------+-----------+------------+------+------------------------------------------+-----------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+------------------------------------------+-----------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | tb_seller | NULL | ref | idx_seller_name,idx_seller_name_sta_addr | idx_seller_name | 403 | const | 1 | 100.00 | NULL |
+----+-------------+-----------+------------+------+------------------------------------------+-----------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
这里面可能用到idx_seller_name,idx_seller_name_sta_addr ;两个索引,但是我们实际只用到了idx_seller_name。我们可以给数据库指定用哪个索引。
mysql> explain select * from tb_seller use index(idx_seller_name_sta_addr) wher
e name='小米科技';
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | tb_seller | NULL | ref | idx_seller_name_sta_addr | idx_seller_name_sta_addr | 403 | const | 1 | 100.00 | NULL |
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
下面这个可能用到的索引就只有一个了。数据库就不用去比较用哪个索引更好了。
8.2 IGNORE INDEX
如果用户只是单纯的想让MySQL忽略一个或者多个索引,则可以使用 ignore index 作为 hint 。
explain select * from tb_seller ignore index(idx_seller_name) where name = '小米科技';
mysql> explain select * from tb_seller ignore index(idx_seller_name) where nam
e='小米科技';
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | tb_seller | NULL | ref | idx_seller_name_sta_addr | idx_seller_name_sta_addr | 403 | const | 1 | 100.00 | NULL |
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
8.3 FORCE INDEX
为强制MySQL使用一个特定的索引,可在查询中使用 force index 作为hint 。
create index idx_seller_address on tb_seller(address);
mysql> explain select * from tb_seller where address='北京市';
+----+-------------+-----------+------------+------+--------------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+--------------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb_seller | NULL | ALL | idx_seller_address | NULL | NULL | NULL | 12 | 91.67 | Using where |
+----+-------------+-----------+------------+------+--------------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
可以看到,这里即便有索引,但是我们并没有走索引,是因为MySQL发现,不走索引比走索引还快。这里因为12条记录11条记录都是北京市,由我们前面的分析索引的使用及优化,走全表扫描更快。
如果我们使用use index呢
mysql> explain select * from tb_seller use index(idx_seller_address) where address='北京市';
+----+-------------+-----------+------------+------+--------------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+--------------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb_seller | NULL | ALL | idx_seller_address | NULL | NULL | NULL | 12 | 91.67 | Using where |
+----+-------------+-----------+------------+------+--------------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
这个时候并没有走索引。因为use index只是给数据库提供参考,而不是强制性的。使用force可以强制性的走索引,即使索引更慢。
mysql> explain select * from tb_seller force index(idx_seller_address) where address='北京市';
+----+-------------+-----------+------------+------+--------------------+--------------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+--------------------+--------------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | tb_seller | NULL | ref | idx_seller_address | idx_seller_address | 403 | const | 11 | 100.00 | NULL |
+----+-------------+-----------+------------+------+--------------------+--------------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)