Mysql常见的几种优化原则

150 阅读5分钟

Mysql常见的优化原则

1、最左匹配原则

首先引用Mysql官方文档的一段话:

Mysql官方文档

MySQL can create composite indexes (that is, indexes on multiple columns). An index may consist of up to 16 columns. For certain data types, you can index a prefix of the column (see Section 8.3.5, “Column Indexes”).

MySQL可以创建联合索引(即, 多列的索引). 一个索引可以包含最多16列. 对于 某些数据类型, 你可以索引列的前缀(这里说的是对于Blob和Text类型, 索引列的前几位就可以, 如INDEX(blob_col(10))

先说结论

  • 联合索引一方面可以帮助我们避免创建重复索引
  • 当我们的查询条件不满足最左匹配原则时,就会出现索引失效的问题
  • 创建联合索引时,尽量使得区分度较大的索引列放在最左侧。区分度计算公式:distinct(index_col)/total_num

下面以一个具体案例说明:

# 创建员工表
CREATE TABLE emp(
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
empno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, #编号
ename VARCHAR(20) NOT NULL DEFAULT "", #名字
job VARCHAR(9) NOT NULL DEFAULT "", #上级编号
mgr MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, 
hiredate DATE NOT NULL, #入职时间
sal DECIMAL(7,2) NOT NULL, 
comm DECIMAL(7,2) NOT NULL,
deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0  #部门编号
) DEFAULT CHARSET=utf8;
# 建立job_deptno联合索引
ALTER TABLE emp ADD INDEX idx_job_deptno (job,deptno)

先看下面三条sql的执行计划

EXPLAIN SELECT id FROM emp WHERE job = 'SALESMAN' AND deptno = '105';
​
# 执行计划
id  select_type  table   type    possible_keys   key             key_len  ref            rows  Extra        
------  -----------  ------  ------  --------------  --------------  -------  -----------  ------  ---------
 1  SIMPLE       emp     ref     idx_job_deptno  idx_job_deptno  32       const,const   99672  Using where  
EXPLAIN SELECT id FROM emp WHERE job = 'SALESMAN';
​
# 执行计划
 id  select_type  table   type    possible_keys   key             key_len  ref       rows  Extra        
------  -----------  ------  ------  --------------  --------------  -------  ------  ------  -------------
 1  SIMPLE       emp     ref     idx_job_deptno  idx_job_deptno  29       const   250124  Using where  
EXPLAIN SELECT id FROM emp WHERE deptno = '105';
​
# 执行计划
 id  select_type  table   type    possible_keys  key     key_len  ref       rows  Extra        
--  -----------  ------  ------  -------------  ------  -------  ------  ------  -------------
  1  SIMPLE       emp     ALL     (NULL)         (NULL)  (NULL)   (NULL)  500249  Using where  

从上面的三条执行计划中我们可以得到什么信息?

  • (1)和(2)都命中了索引,(3)未命中了索引。这是因为第三条sql的查询条件中未包含联合索引中的最左列(job)
  • 通过(1)和(2)的索引长度(key_len)可以看出,(2)中的索引仅仅使用了(job)索引。这表明:idx_job_deptno该索引的效果相当于创建了两个索引:idx_job、idx_job_deptno

2、避免重复索引

还是以上述案例说明:假设我们要查询某个领导下的所有员工信息。

我们很自然地想到建立(job)字段的索引,但其实完全没有必要

因为我们上面已经解释过,创建下面的联合索引其实相当于创建了两个索引

这其实给我们一个启示,在平时业务中建立索引时尽量建立联合索引减少索引个数

INDEX idx_job_deptno (job,deptno)
==
INDEX(job)
INDEX(job,deptno)

3、禁止索引运算

如下面的sql会导致索引失效

首先添加相关索引部门id索引

ALTER TABLE emp ADD INDEX idx_deptno (deptno)
EXPLAIN SELECT id FROM emp WHERE deptno + 1 = 105;
​
id  select_type  table   type    possible_keys  key     key_len  ref       rows  Extra        
---  -----------  ------  ------  -------------  ------  -------  ------  ------  -------------
 1  SIMPLE       emp     ALL     (NULL)         (NULL)  (NULL)   (NULL)  500249  Using where  
 
EXPLAIN SELECT id FROM emp WHERE deptno > 105;
​
id  select_type  table   type    possible_keys  key     key_len  ref       rows  Extra        
------  -----------  ------  ------  -------------  ------  -------  ------  ------  -------------
 1  SIMPLE       emp     ALL     idx_deptno     (NULL)  (NULL)   (NULL)  500249  Using where  
 
 
 EXPLAIN SELECT id FROM emp WHERE deptno != 105;
 id  select_type  table   type    possible_keys  key     key_len  ref       rows  Extra        
------  -----------  ------  ------  -------------  ------  -------  ------  ------  -------------
 1  SIMPLE       emp     ALL     idx_deptno     (NULL)  (NULL)   (NULL)  500249  Using where 

4、避免非前缀匹配的模糊查询

添加idx_name索引

ALTER TABLE emp ADD INDEX idx_name (ename)

前缀匹配查询(推荐)

EXPLAIN SELECT * FROM emp WHERE ename LIKE 'aAAa%';
​
id  select_type  table   type    possible_keys  key       key_len  ref       rows  Extra        
------  -----------  ------  ------  -------------  --------  -------  ------  ------  -------------
1  SIMPLE       emp     range   idx_name       idx_name  62       (NULL)      38  Using where  

索引失效场景

EXPLAIN SELECT * FROM emp WHERE ename LIKE '%aAAa%';
​
id  select_type  table   type    possible_keys  key     key_len  ref       rows  Extra        
------  -----------  ------  ------  -------------  ------  -------  ------  ------  -------------
1  SIMPLE       emp     ALL     (NULL)         (NULL)  (NULL)   (NULL)  500249  Using where  

5、避免子查询

目标:查询指定部门下的员工id

先看子查询

SELECT emp.id FROM emp WHERE deptno IN (SELECT deptno FROM dept);
​
# 执行时间:0.337sec

连接查询

SELECT emp.id FROM emp LEFT JOIN dept ON emp.deptno = dept.`deptno`;# 执行时间:0.009sec

事实胜于雄辩!

为什么上述子查询效率会比连接查询慢?

我们再来看下第一个子查询,一般我们都会认为它会分为两步:首先执行内查询,继而执行外查询

SELECT deptno FROM dept;
SELECT emp.id FROM emp WHERE deptno IN (a,...,n);

然而mysql优化器会改写为这样:

SELECT emp.id FROM emp WHERE deptno exsit (SELECT deptno FROM dept);

这样会有个问题,如果外层表是一个非常大的表,对于外层查询的每一行,子查询都得执行一次,这个查询的性能会非常差

6、延迟关联查询

先说核心思想:在分页查询这种场景下,通过覆盖索引查询获取所需的主键,减少回表次数,再根据主键查询真正所需要的数据

目标:查询工号排序第200000个~200100个员工

EXPLAIN SELECT emp.ename FROM emp ORDER BY deptno LIMIT 200000, 100;
​
# 执行时间:0264sec
​
id  select_type  table   type    possible_keys  key     key_len  ref       rows  Extra           
--  -----------  ------  ------  -------------  ------  -------  ------  ------  ----------------
 1  SIMPLE       emp     ALL     (NULL)         (NULL)  (NULL)   (NULL)  500249  Using filesort  

遇到偏移量比较大的分页查询时,上述查询方式有什么弊端

如limit 200000,20 取第200000个~200100条记录,mysql会取出200100条记录然后将前200000条记录丢弃,这无疑是一种巨大的性能浪费。

此时我们采取延迟关联的方式来优化,即先通过覆盖索引查询所需记录的主键索引,此时无需回表;然后通过主键一次性查出相应的记录

EXPLAIN SELECT emp.ename FROM emp INNER JOIN (SELECT id FROM emp  ORDER BY deptno LIMIT 200000, 100) AS temp ON temp.id = emp.id;
​
#执行时间:0.071sec
​
id  select_type  table       type    possible_keys  key         key_len  ref        rows  Extra        
--  -----------  ----------  ------  -------------  ----------  -------  -------  ------  -------------
 1  PRIMARY      <derived2>  ALL     (NULL)         (NULL)      (NULL)   (NULL)      100               
 1  PRIMARY      emp         eq_ref  PRIMARY        PRIMARY     4        temp.id       1               
 2  DERIVED      emp         index   (NULL)         idx_deptno  3        (NULL)   200100  Using index  

我们可以从对应的执行计划中看出明显的差距。 如《高性能mysql》中所述。