MySQL 初级优化

本篇概要:

  • 1. 加载官方数据集、explain 指标、分页常用优化;
  • 2. 索引优化;
    • 2.1 字符串查询、BTree 索引;
    • 2.2 唯一索引、组合索引;
    • 2.3 Limit 优化;
  • 3. 利用子查询、右连接进行聚合查询优化。

1. 加载官方数据集、explain 指标、分页常用优化;

安装 MySQL 测试数据集:https://github.com/datacharmer/test_db

# 下载资源
cd /usr/local/src
git clone https://github.com/datacharmer/test_db

# 导入
mysql uroot -pasdf < employees.sql

explain 命令:优化 SQL 语句、查看 SQL 是如何执行、优化指标

  • 官方文档 1 :https://dev.mysql.com/doc/refman/5.7/en/explain-output.html
  • 文档 2:https://dev.mysql.com/doc/workbench/en/wb-performance-explain.html
# 文档 2:查找关键字 EXPLAIN Join Types
# 结果集依次好坏:
# system > const > eq_ref > ref > fulltext > ref_or_null 
# > index_merge > unique_subquery > index_subquery > range > index > ALL

# 举例说明:
EXPLAIN select * from employees
# type 就是指标,执行上面 SQL 语句的 type 为 ALL
# 问题 1:SQL 查询语句用 * 是不好的,在性能上和带宽上都是有问题的。用到什么字段写什么字段

# 优化:根据主键排序
EXPLAIN select * from employees ORDER BY emp_no 
# type 上升为第二级 index,完成了第一步优化

# 第三级 range:在范围之内,取出指定范围内的行
EXPLAIN select * from employees ORDER BY emp_no limit 0, 10
# rows 列:MySQL 预估要检查的行数,只能供参考,并不是实际的行数
# 以下两句话效果不一样
EXPLAIN select * from employees ORDER BY emp_no limit 0,10;
EXPLAIN select * from employees ORDER BY emp_no limit 50,10
# 在做 MySQL 分页的时候,limit 的第一个参数越大的时候,rows 也会越来越大

分页常见方法

# 以第二页为例(实际开发第二页不需要这么做,可以设置个阈值,当分页大于多少时开始启用如下 SQL 语句,小于的话直接 limit 就行)
# 这里用第二页举例仅仅是为了演示方便
# 优化方式:主键里面去取主键
# 主键大于上一页的最大值(正排序,越往下越大)
select xx,yy from employees where emp_no >= 
(select emp_no from employees ORDER BY emp_no limit 10,1) 
ORDER BY emp_no limit 10

# 分析
EXPLAIN select emp_no from employees ORDER BY emp_no limit 10,1
# Extra 列,代表 MySQL 完成查询的所需的额外信息值:比如创建临时表,使用索引等
# Using index :从**索引树**中取值而不需要从实际的 rows 来检索
# Using where: 限制了那些行进行匹配

EXPLAIN select xx,yy from employees where emp_no >= 
(select emp_no from employees ORDER BY emp_no limit 10,1) 
ORDER BY emp_no limit 10
# type 出现了 range,比 index 更加高一层
# range 是在指定的行,通过索引去选择行

实战常见做法(场景不同做法不同)

# 在"下一页"参数中带上 >= 的数字
# 比如页码 1 的 url 是 a.php?page=1    
# 页码 2 的参数是 a.php?page=2&no=10010
EXPLAIN select xx,yy from employees where emp_no >= 10010
# 此时 MySQL 运行速度是非常快的

# 另外的做法(优化),不仅仅是从数据库入手,需要借助第三方,比如 redis
# 比如以 50 页为一段,把各个 50 页的 ID 值写入 redis 缓存(hash数据类型)
# 存入前 50 个 ID
hset xxx1 page1 10010,10011,10020
hset xxx2 page2
hset xxx3 page3
# 查询的时候,使用索引的子查询(value IN (SELECT key_column FROM ...))
# 分页时从缓存中取范围,则进入 range 指标
# 如果删掉了数据,需要去更新 redis 缓存

# 如果查询并发高,写不高,直接把前 20 页数据放入 redis 缓存中。定期更新缓存

# 借助类似 elasticsearch 等第三方

# 数据过大,需要分库分表。使用数据库中间件

2. 索引优化;

2.1 字符串查询、BTree 索引;

# 用字符串作为条件
select count(*) from employees where last_name = 'Brender'
# 查询出 196 条记录

# 以下是性能比较低的查询方式(如果数据有个几百万)
# EXPALIN 一下,type 为 ALL,是最丑陋的一个指标
select * from employees where last_name = 'Brender' limit 10

# 继续分析
EXPLAIN select xx from employees where last_name = 'Brender' order by emp_no limit 10;
# order by 主键索引,type 为 index,需要继续优化
# last_name 经常要用来查询,所以建立索引,索引名自定义,栏位名和字段名匹配
# 索引类型为 normal,索引方式选择 BTREE(不能选 HASH)

## BTree 索引(平衡多路查找树)
# 此时再 EXPLAIN,type 为 ref
# 参考:https://dev.mysql.com/doc/refman/5.7/en/explain-output.html#jointype_ref
# 通过 BTree,可以快速定位行

# Extra 为 Using index condition
# possible_keys:可能使用哪个索引在该表中找到行(这里是 last_name)
# key:实际决定使用的索引(这里是 last_name)
# key_len:使用索引(字节)长度,int 占 4 字节,而varchar(n) 需要原长度 n+2,如果允许空 n+3
# 一般索引字段不要为空
# 字符编码 latin1 的 1 字节是 1 长度,gbk 是 1:2,utf8 是 1:3
# select_type:SIMPLE 最简单的查询(不包含子查询或 union)

# 各个引擎支持的索引类型(如下图)
# https://dev.mysql.com/doc/refman/5.7/en/create-index.html

MySQL 初级优化_第1张图片

2.2 唯一索引、组合索引;

唯一索引

# 不重复的字段建立索引,就是唯一索引
# 在 employees 表中加一个字段 ids,代表是身份证,类型 char(18)
# 执行以下 SQL
update employees set ids = 31010119901212 + emp_no

# 如果要查询身份证
EXPLAIN select xx from employees where ids = '31010119911218'
# 这是完全没优化的代码,type 为 ALL

# 分别给 ids 创建 普通索引 和 唯一索引
# 普通索引的 type 为 ref
# 唯一索引 type 进一步提升为 const
# 比普通索引更快,而且重复值无法插入

count(*)

# 文档
# https://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html#function_count
# count(*) 在 MyISAM 模式下非常快,行数已经保存在了引擎里,直接取出来并不需要去扫描表
# 在 INNODB 下,可能会因为其它连接在做事务而产生不一样的结果

# count(*) 代表统计所有数据的行数  
select count(*) from employees 

# count(ids) 这个统计是不包含 ids = null 的数据的
select count(ids) from employees 
# 如下操作后再 count(ids),会发现记录少了一条
update employees set ids = null where emp_no = 10001
# 之后如果有需求,查询填过身份证的条数,就直接 count(ids)

联合索引

# 场景:查询出雇佣时间 hire_date 在某个时间的数据
# 由于雇用时间会重复,不能建唯一索引
# 如果 hire_date 需要经常查询,需要对它创建一个普通索引
EXPLAIN select xx from employees where hire_date = '1986-12-01' 
order by emp_no LIMIT 0,50
# type 为 ref

# 追加需求(ids 和 hire_date 都有索引)
EXPLAIN select xx from employees where hire_date >= '1986-12-01' 
order by hire_date, ids LIMIT 0,50
# 此时 type 降级为 ALL
# 查看概况 Creating sort index 需要花去 99% 以上的时间
# 如果经常需要对这两个字段进行排序的话
# 此时需要创建联合索引,创建索引的时候把 hire_date 和 ids (字段先后顺序和查询语句相同)同时勾上
# type 升级到 range
# possible keys 为 hire_date, hire_date_2
# key 为 hire_date_2(hire_date_2 不是字段名,是索引名)
# 联合索引不能随意创建,创建的时候字段顺序要要和 SQL 语句的字段顺序一样
# 查询条件的字段也是(注意是 where hire_date >=,联合索引也是 hire_date 在前)
# 如果查询语句是 where ids >=,那需要调整联合索引字段为 ids,hire_date

# 不要所有列都创建索引,否则 insert 和 update 就会很慢,因为要创建或者更新索引

2.3 Limit 优化;

官方示例数据库
MySQL 初级优化_第2张图片

# 如果不加 limit,有索引 type 还是 ALL,key为 NULL
# 加上 limit 50,type 升级为 range
EXPLAIN select * from employees where hire_date > '1985-02-18' limit 50

# 接下去用到 salaries 表
# 需求:查询出 hire_date > '1985-02-18' 的员工的薪水情况,要求是按 emp_no 倒排序
# 原始 SQL 如下
EXPLAIN select a.emp_no, a.salary,from_date from salaries a 
left join employees b 
on a.emp_no = b.emp_no
where b.hire_date > '1985-02-18' 
order by a.emp_no limit 50
# 第一行 b 表 type 为 range,Extra 包含 Using temporary(临时表),Using filesort(额外排序方式)
# 但是出现以上两项说明需要优化
# 第二行 a 表 type 为 ref 
# 实际运行以上的 SQL 语句,几乎无法运行成功
# 修改写法:
EXPLAIN select a.emp_no,a.salary,from_date from salaries a 
left join employees b 
on a.emp_no=b.emp_no
where hire_date > '1985-02-18' 
order by b.emp_no limit 50
# 第一行 b 表 type 为 index,row 为 10,Extra 为 using where
# 第二行 a 表 type 为 ref
# 所以有时候不能光看 type 的指标,还要注意 Extra

3. 利用子查询、右连接进行聚合查询优化。

你可能感兴趣的:(MySQL)