性能下降SQL 慢,执行时间长,等待时间长
索引
索引是帮助MySQL高效获取的数据结构。
MySQL的 BTree 索引使用的是B树中的 B+Tree。
B树和B+树的区别
聚簇索引
聚簇索引即是主键索引,其它辅助索引则为非聚簇索引。
一二级索引:
创建
CREATE [UNIQUE] INDEX [indexName] ON table_name(column)
CREATE INDEX idx_customer_name ON customer(customer_name);
CREATE UNIQUE INDEX idx_customer_no ON customer(customer_no);
CREATE INDEX idx_no_name ON customer(customer_no,customer_name);
# 随表一起建索引
CREATE TABLE customer (id INT(10) UNSIGNED AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name VARCHAR(200),
PRIMARY KEY(id),
KEY (customer_name), # 单值索引
UNIQUE (customer_name), # 唯一索引
KEY (customer_no,customer_name) # 复合索引
);
删除
DROP INDEX [indexName] ON mytable;
查看
SHOW INDEX FROM table_name\G
使用 Alter
# 该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL。
ALTER TABLE tbl_name ADD PRIMARY KEY (column_list)
# 这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)。
ALTER TABLE tbl_name ADD UNIQUE index_name (column_list)
# 添加普通索引,索引值可出现多次。
ALTER TABLE tbl_name ADD INDEX index_name (column_list)
# 该语句指定了索引为 FULLTEXT ,用于全文索引。
ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list)
哪些情况需要创建索引?
哪些情况不要创建索引?
使用 EXPLAIN
关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈。
我们来实操一下:Explain + SQL语句
mysql> explain select * from t1 join t2;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
| 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 8 | 100.00 | NULL |
| 1 | SIMPLE | t2 | NULL | ALL | NULL | NULL | NULL | NULL | 8 | 100.00 | Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
2 rows in set, 1 warning (0.00 sec)
主要字段总览:执行计划字段
列名 | 描述 |
---|---|
id | 在一个大的查询语句中每个SELECT关键字都对应一个唯一的id |
select_type | SELECT关键字对应的那个查询的类型 |
table | 表名 |
partitions | 匹配的分区信息 |
type | 针对单表的访问方法 |
possible_keys | 可能用到的索引 |
key | 实际上使用的索引 |
key_len | 实际使用到的索引长度 |
ref | 当使用索引列等值查询时,与索引列进行等值匹配的对象信息 |
rows | 预估的需要读取的记录条数 |
filtered | 某个表经过搜索条件过滤后剩余记录条数的百分比 |
Extra | 一些额外的信息 |
select 查询的序列号,包含一组数字,表示查询中执行 select 子句或操作表的顺序
我们写的查询语句一般都以SELECT关键字开头,比较简单的查询语句里只有一个SELECT关键字,但是下边两种情况下在一条查询语句中会出现多个SELECT关键字:
查询语句中每出现一个SELECT关键字,MySQL就会为它分配一个唯一的id值。这个id值就是EXPLAIN语句的第一个列。对于连接查询来说,一个SELECT关键字后边的FROM子句中可以跟随多个表,所以在连接查询的执行计划中,每个表都会对应一条记录,但是这些记录的id值都是相同的。
id号每个号码,表示一趟独立的查询。一个sql 的查询趟数越少越好。
查询优化器可能对涉及子查询的查询语句进行重写,从而转换为连接查询。所以如果我们想知道查询优化器对某个包含子查询的语句是否进行了重写,直接查看执行计划就好了
mysql> explain select * from t1 where a in (select a from t2);
+----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+
| 1 | SIMPLE | t1 | NULL | ALL | PRIMARY | NULL | NULL | NULL | 8 | 100.00 | NULL |
| 1 | SIMPLE | t2 | NULL | eq_ref | PRIMARY | PRIMARY | 4 | luban.t1.a | 1 | 100.00 | Using index |
+----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
可以看到,虽然我们的查询语句是一个子查询,但是执行计划中 t1 和 t2 表对应的记录的 id 值全部是1,这就表明了查询优化器将子查询转换为了连接查询。
UNION会把多个查询的结果集合并起来并对结果集中的记录进行去重(使用的是内部的临时表)
mysql> explain select * from t1 union select * from t2;
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| 1 | PRIMARY | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 8 | 100.00 | NULL |
| 2 | UNION | t2 | NULL | ALL | NULL | NULL | NULL | NULL | 8 | 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.00 sec)
正如上边的查询计划中所示,UNION子句是为了把 id 为1的查询和 id 为2的查询的结果集合并起来并去重,所以在内部创建了一个名为
的临时表(就是执行计划第三条记录的table列的名称),id 为 NULL 表明这个临时表是为了合并两个查询的结果集而创建的。
UNION ALL 就不需要为最终的结果集进行去重,它只是单纯的把多个查询的结果集中的记录合并成一个并返回给用户,所以也就不需要使用临时表。所以在包含UNION ALL子句的查询的执行计划中,就没有那个id为NULL的记录
查询的类型,主要是用于区别 普通查询、联合查询、子查询等的复杂查询。
SIMPLE
:查询语句中不包含UNION或者子查询的查询都算作是SIMPLE类型。连接查询也算是SIMPLE类型explain select * from t1;
explain select * from t1 join t2;
PRIMARY
:对于包含UNION、UNION ALL或者子查询的大查询来说,它是由几个小查询组成的,其中最左边的那个查询的 select_type 值就是 PRIMARY (最外层)explain select * from t1 where a in (select a from t2) or c = 'c';
UNION
:对于包含UNION或者UNION ALL的大查询来说,它是由几个小查询组成的,其中除了最左边的那个小查询以外,其余的小查询的 select_type 值就是 UNION。mysql> explain select * from t1 union select * from t2;
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| 1 | PRIMARY | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 8 | 100.00 | NULL |
| 2 | UNION | t2 | NULL | ALL | NULL | NULL | NULL | NULL | 8 | 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.00 sec)
UNION RESULT
:MySQL选择使用临时表来完成UNION查询的去重工作,针对该临时表的查询的select_type就是UNION RESULTSUBQUERY
:非相关子查询,由于select_type为SUBQUERY的子查询由于会被物化,所以只需要执行一遍。explain select * from t1 where a in (select a from t2) or c = 'c';
DEPENDENT SUBQUERY
:相关子查询,select_type为DEPENDENT SUBQUERY的查询可能会被执行多次explain select * from t1 where a in (select a from t2 where t1.a = t2.a) or c = 'c';
DERIVED
:在FROM列表中包含的子查询被标记为DERIVED(衍生) MySQL会递归执行这些子查询, 把结果放在临时表里。explain select * from (select a, count(*) from t2 group by a ) as deliver1;
MATERIALIZED
:当查询优化器在执行包含子查询的语句时,选择将子查询物化之后与外层查询进行连接查询时,该子查询对应的select_type属性就是MATERIALIZED。explain select * from t1 where a in (select c from t2 where e = 1);
显示查询使用了何种类型,从最好到最差依次是:system>const>eq_ref>ref>range>index>ALL
system
:当表中只有一条记录并且该表使用的存储引擎的统计数据是精确的,比如MyISAM、Memory,那么对该表的访问方法就是system。CREATE TABLE t(i int) Engine=MyISAM;
INSERT INTO t VALUES(1);
explain select * from t;
const
:当我们根据主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法就是const。explain select * from t1 where a = 1;
eq_ref
:在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的(如果该主键或者唯一二级索引是联合索引的话,所有的索引列都必须进行等值比较),则对该被驱动表的访问方法就是 eq_ref explain select * from t1 join t2 on t1.a = t2.a;
ref
:当通过普通的二级索引列与常量进行等值匹配时来查询某个表,那么对该表的访问方法就可能是ref。explain select * from t1 where b = 1;
ref_or_null
:当对普通二级索引进行等值匹配查询,该索引列的值也可以是NULL值时,那么对该表的访问方法就可能是 ref_or_nullexplain select * from t1 where b = 1 or b is null;
index_merge
:索引合并,通常出现在有 or 的关键字的sql中explain select * from t1 where a = 1 or b = 1;
unique_subquery
:如果查询优化器决定将 IN 子查询转换为 EXISTS 子查询,而且子查询可以使用到主键进行等值匹配的话,那么该子查询执行计划的 type 列的值就是 unique_subquery。子查询关联唯一索引explain select * from t1 where c in (select a from t2 where t1.e = t2.e) or a = 1;
index_subquery
:与 unique_subquery 类似,只不过访问子查询中的表时使用的是普通的索引。explain select * from t1 where c in (select b from t2 where t1.e = t2.e) or a = 1;
range
:一般就是在你的where语句中出现了between、<、>、in等的查询explain select * from t1 where a > 1;
index
:当我们可以使用覆盖索引,但需要扫描全部的索引记录时,该表的访问方法就是index。explain select b from t1;
ALL
:全表扫描备注:一般来说,得保证查询至少达到range级别,最好能达到ref。
从最好到最差依次是:system>const>eq_ref>ref>range>index>ALL
possible_keys
:显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。
key
:实际使用的索引。如果为NULL,则没有使用索引。查询中若使用了覆盖索引,则该索引和查询的select字段重叠
不过有一点比较特别,就是在使用index访问方法来查询某个表时,possible_keys列是空的,而key列展示的是实际使用到的索引。
possible_keys列中的值并不是越多越好,可能使用的索引越多,查询优化器计算查询成本时就得花费更长时间,所以如果可以的话,尽量删除那些用不到的索引
key_len列表示当优化器决定使用某个索引执行查询时,该索引记录的最大长度,它是由这三个部分构成的:
key_len字段能够帮你检查是否充分的利用上了索引,数字越大,查询一般越快。
当使用索引列等值匹配的条件去执行查询时,也就是在访问方法是const、eq_ref、ref、ref_or_null、unique_subquery、index_subquery其中之一时,ref列展示的就是与索引列作等值匹配的东西是什么,比如只是一个常数或者是某个列。
如果查询优化器决定使用全表扫描的方式对某个表执行查询时,执行计划的rows列就代表预计需要扫描的行数,如果使用索引来执行查询时,执行计划的rows列就代表预计扫描的索引记录行数。
代表查询优化器预测在这扫描的记录中,有多少条记录满足其余的搜索条件,是百分比。
Extra列是用来说明一些额外信息的,我们可以通过这些额外信息来更准确的理解MySQL到底将如何执行给定的查询语句。
No tables used
:当查询语句的没有FROM子句时将会提示该额外信息。explain select 1;
Impossible WHERE
:查询语句的WHERE子句永远为FALSE时将会提示该额外信息。explain select b from t1 where 1=0;
No matching min/max row
:当查询列表处有MIN或者MAX聚集函数,但是并没有符合WHERE子句中的搜索条件的记录时,将会提示该额外信息。explain select max(a) from t1 where a=100;
Using index
:当我们的查询列表以及搜索条件中只包含属于某个索引的列,也就是在可以使用索引覆盖的情况下,在Extra列将会提示该额外信息。explain select d from t1 where b =1;
Using index condition
:有些搜索条件中虽然出现了索引列,但却不能使用到索引(在MySQL 5.6版本后加入的新特性)explain select * from t1 where b =1 and b like '%1';
Using where
:当我们使用全表扫描来执行对某个表的查询,并且该语句的WHERE子句中有针对该表的搜索条件时,在Extra列中会提示上述额外信息。explain select * from t1 where e = 1;
Using join buffer (Block Nested Loop)
:在连接查询执行过程中,当被驱动表不能有效的利用索引加快访问速度,MySQL一般会为其分配一块名叫 join buffer的内存块来加快查询速度。explain select * from t1 join t2 on t1.e = t2.e;
Using filesort
:很多情况下排序操作无法使用到索引,只能在内存中(记录较少的时候)或者磁盘中(记录较多的时候)进行排序,这种在内存中或者磁盘上进行排序的方式统称为文件排序(英文名:filesort)。如果某个查询需要使用文件排序的方式执行查询,就会在执行计划的Extra列中显示Using filesort提示。explain select * from t1 order by e;
Start temporary、End temporary
:查询优化器会优先尝试将IN子查询转换成 semi-join,而 semi-join又有好多种执行策略,当执行策略为 DuplicateWeedout时,也就是通过建立临时表来实现为外层查询中的记录进行去重操作时,驱动表查询执行计划的 Extra列将显示 Start temporary提示,被驱动表查询执行计划的 Extra列将显示 End temporary提示explain select * from t1 where a in (select e from t2 where e = 1);
FirstMatch(表名)
:在将In子查询转为semi-join时,如果采用的是FirstMatch执行策略,则在被驱动表执行计划的Extra列就是显示FirstMatch(tbl_name)提示。explain select * from t1 where a in (select c from t2 where c = 1);
主要字段:id 、type、key_len、rows、Extra
性能按 type 排序
system > const > eq_ref > ref > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
性能按Extra排序
慢查询日志
MySQL 的慢查询日志是 MySQL 提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过 long_query_time 值的SQL,则会被记录到慢查询日志中。
插入大量语句优化思路?
通过慢查询日志,针对查询慢的、查询次数多的进行优化
全值匹配
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND deptid=4 AND emp.name = 'abcd';
#全值索引
CREATE INDEX idx_age_deptid_name ON emp(age,deptid,NAME);
索引失效
接下来介绍几种索引失效场景
最左侧字段为 age,所以。。。
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.deptid=1 AND emp.name = 'abcd' ;
CREATE INDEX idx_name ON emp(NAME);
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name LIKE 'abc%' ;#走的range
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE LEFT(emp.name,3) = 'abc';#失效了
如果只给出后缀或者中间的某个字符串,是用不到索引的
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name LIKE '%abc%' ;
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name LIKE '%abc' ;
不能使用索引中范围条件右边的列(emp.name失效),应该把这种字段放到末尾
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND emp.deptId>20 AND emp.name = 'abc' ;
#应该建立
create index idx_age_name_deptid on emp(age,name,deptid)
不过在使用联合进行范围查找的时候需要注意,如果对多个列同时进行范围查找的话,只有对索引最左边的那个列进行范围查找的时候才能用到B+树索引
CREATE INDEX idx_name ON emp(NAME)
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name <> 'abc'
UPDATE emp SET age =NULL WHERE id=123456;
EXPLAIN SELECT * FROM emp WHERE age IS NULL
EXPLAIN SELECT * FROM emp WHERE age IS NOT NULL
建立索引,考虑以下:
SELECT count(DISTINCT(concat(first_name, left(last_name, 4))))/count(*) AS Selectivity FROM employees.employees; -- 0.9007
ALTER TABLE employees.employees ADD INDEX `first_name_last_name4` (first_name, last_name(4));
遵循以下原则:
示例 left join:给被驱动表(这里为 book)添加索引,给驱动表(这里为 class),无法避免全表扫描
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
# 添加索引优化
ALTER TABLE `book` ADD INDEX Y ( `card`);
尽量不要使用 not in 或者 not exists
SELECT SQL_NO_CACHE age,count(*) FROM emp a
WHERE id NOT IN(SELECT ceo FROM dept b2 WHERE ceo IS NOT NULL)
用 left outer join on xxx is null 替代
EXPLAIN SELECT SQL_NO_CACHE age,count(*) FROM emp a
LEFT OUTER JOIN dept b ON a.id =b.ceo
WHERE b.ceo IS NULL
explain select SQL_NO_CACHE * from emp order by age,deptid;
explain select SQL_NO_CACHE * from emp order by age,deptid limit 10;
注意:当联合索引左边列的值为常量,也可以使用后边的列进行排序
create index idx_age_deptid_name on emp (age,deptid,name)
explain select * from emp where age=45 order by deptid; #正常使用索引
explain select * from emp where age=45 order by name,deptid; # name在前即可使用索引
explain select * from emp where deptid=45 order by age; # where 即未满足条件
explain select * from emp where age=45 order by deptid desc, name desc ;#正常
explain select * from emp where age=45 order by deptid asc, name desc ;#失效
当无法避免 Usingfilesort 时
首先:ORDER BY子句,尽量使用 Index 方式排序,避免使用 FileSort 方式排序
filesort 有两种算法:
优化策略:
group by 使用索引的原则几乎跟 order by一致 ,唯一区别是 groupby 即使没有过滤条件用到索引,也可以直接使用索引。
最后使用索引的手段:覆盖索引
什么是覆盖索引?
简单说就是,select 到 from 之间查询的列 <= 使用的索引列+主键