索引优化实战
索引失效
数据量太大的时候不如走全表扫描,不用回表
类型格式不同导致索引失效
如 varchar 类型,查询条件未加单引号,会导致索引失效
in 和 or 在表数据量比较大的情况会走索引,在表记录不多的情况下会选择全表扫描
索引下推
如使用 name like "dd%" and age = 3
时:
- 5.6 之前会先查询 name 的二级索引获取到
主键 id
,再去聚簇索引去筛选age=3
获取数据 - 5.6 之后会查询 name 的二级索引同时筛选 age(只会筛选联合索引的那几个字段)再获取到
主键 id
,再去聚簇索引获取数据
like
大多数情况会走索引下推
MySQL 如何选择是否走索引
1 | set session optimizer_trace = 'enabled=on',end_markers_in_json = on; |
常见 sql 深入优化
范围查询
1 | select * |
order by 与 group by 优化
where 中联合索引都出现且为常量,order by 就会走索引
1 | -- 联合索引 (name,age,idcard) |
Using filesort
区别
- using index 使用的二级索引,只加载部分数据到排序内存 sort_buffer
- using filesort 使用的聚簇索引,加载所有数据到排序内存 sort_buffer(** 聚簇索引有表所有数据 **)
filesort 排序方式
- 单路排序,把所有查询结果放入到内存直接通过排序字段排序
- 双路排序,只把 id 和排序字段放入进入排序
1 | -- trace |
索引设计原则
- 代码先行,索引后上,主体业务开发完,将所有 sql 查看来评估建立索引
- 联合索引尽量覆盖条件,大部分情况 mysql 只会选择一个索引
- 不要在小基数字段上建立索引,比如性别
- 长字符采用前缀索引,列如
name(255)
时,创建索引KEY index(name(20),age,idcard)
- where 与 order by 冲突优先 where
- 基于慢 sql 查询优化,默认执行超过 10 秒即记录到慢查询日志
- 读多写少场景可以多设计几个索引,读多写多的情况一两个复杂索引就可以了,因为写入需要维护索引树
实战案例
因为用了两个范围查询,所以可以设计一个七天内登陆的标记字段
分页查询优化
1 | select * |
实际查询的是 1-100010 条记录,再舍弃前 10w 条记录
根据自增且连续的主键排序分页查询
1 | select * |
连续且自增 id 才能这样使用,中间不能丢数据
根据非主键字段分页排序
1 | select * |
数据量过大,鉴于非主键索引需要回表,所以 mysql 不会走索引,而是全表扫描,所以排序也走的 filesort 文件排序
1 | select * |
使用了索引,且走索引排序
关联表优化
aa 表和 bb 表一样,主键索引 id,非主键索引 cid,且 aa 表 1w 条数据,bb 表 100 条
嵌套循环连接 NLJ
inner 内连接,主键索引条件,走的 ** 磁盘扫描 **
1 | select * |
匹配过程:
- bb 全表扫描所有数据
- aa 是主键索引扫描,用的 bb 的 id 去查询 aa 的 id,
aa 只查询了 100 次,加上 bb 的一百次,即一共查询 200 次
基于块的嵌套循环连接 BNL
inner 内联连接,无索引,走的 ** 内存对比 **
1 | select * |
匹配过程:
- bb 数据放入 join_buff 中,如果 bb 数据过大,join_buff 会分段放入
- 将 aa 每一行取出与 join_buff 数据对比
- 返回满足 join 条件数据
此时 ** 磁盘扫描 ** 了 bb 表 100 次,aa 的扫描次数为 1w=1w 次,即一共 1w 零 1 百次
但是 join_buff 会在 ** 内存 ** 做 100*1w=100w 次对比,如果没有索引走的 NLJ,则需要 ** 磁盘扫描对比 **,即扫描 100w 零 100 次
sql 优化
- 小表驱动大表
- 尽量在关联字段添加索引
- straight_join 同 inner join,但会指定让前表作为驱动表,inner join 是 mysql 自己判断谁是小表
小表定义
参与关联的数据更少才是小表
in 和 exsites 优化
原则还是小表驱动大表
1 | -- 当 b 查出来数据集小于 a 时,in>exists |
exists 最好还是替换成 join
count(*) 优化
1 | count(1) |
- 字段有索引,count(*)=count(1)>count(name)>count(id),二级索引比主键索引数据少,同时 count(1) 会计算二级索引更快,会走二级索引,同时不会取
name,直接计数 - 字段无索引,count(*)=count(1)>count(id)>count(name),同上,走主键索引
count(*) 是专门做了优化,不取值直接按行累加
常用优化方法
mysql 自己维护行数
myisam 会将总行数存入磁盘
innodb 因为有 mvcc 机制,每次事务都会实时查询
show table status
将总数维护到 Redis 里
添加数据库计数表
阿里 MySQL 规范
强制
- 业务上有唯一特性的字段,即使是组合字段,也必须创建唯一索引
- 超过三表以上禁止 join,尽量在 java 执行
- varchar 字段上建立索引,必须制定索引长度,没必要全建索引
- like 必须走右模糊,左 / 全模糊走搜索引擎
- 禁止使用存储过程
推荐
- 超过 500 万行或者单表超过 2G,才推荐分库分表
- in 操作能避免就避免,in 后面集合元素数量控制在 1000 内
字段类型的选择
- 大类型,数字、字符串、时间、二进制
- 具体类型,有无符号、取值范围、变长定长
- 无符号,可以取消负数范围,扩大正数范围
- int(11),为显示宽度,即补零用的