MyISAM存储引擎
InnoDB存储引擎
为什么建议InnoDB表必须建主键,并且推荐使用整型的自增主键?
- 因为InnoDB需要创建一个唯一的索引,叶子节点用于存放所有的数据,如果建表的时候没有指定,InnoDB会找表中有没有不重复的字段,将其作为主键索引,如果都没有,会创建一个伪例(相当于rowid)作为自增的主键索引
- 因为整型相对于字符串,占用的字节数少,页签中可以存放更多的索引数据,自增是因为索引是排好序的,比如在2和6中 突然插入了一个5,InnoDB会去重新排序索引,影响数据库性能
为什么非主键索引结构叶子节点存储的是主键值?
- 保证表数据的一致性,修改数据只需要维护主键索引叶子节点里的数据
- 只有主键索引叶子节点存储所有的数据,可以节省存储空间,其余的索引的叶子节点存放主键值,在找到结果之后,重新回主键索引,获取这个主键的所有数据
explain中的列
id列
select_type列
select_type表示对应行是简单还是复杂的查询。
simple
简单查询。查询不包含子查询和union
primary
复杂查询中最外层的select
subquery
包含在select中的子查询(不在from子句中)
derived
union
在union中的第二个和随后的select
table列
这一列表示explain的一行正在访问哪个表
type列
这一列表示关联类型或访问类型,即MySQL决定如何查找表中的行,查找数据行记录的大概范围
依次从最优到最差分别为:system>const>eq_ref>ref>range>index>ALL
key_len列
key_len计算规则如下:
字符串
char(n)和varchar(n),5.0.3以后版本中,n均代表字符数,而不是字节数,如果是utf-8,一个数字或字母占1个字节,一个汉字占3个字节
- char(n):如果存汉字长度就是3n字节
- varchar(n):如果存汉字则长度是3n+2字节,加的2字节用来存储字符串长度,因为varchar是变长字符串
数值类型
- tinyint:1字节
- smallint:2字节
- int:4字节
- bigint:8字节
时间类型
- date:3字节
- timestamp:4字节
- datetime:8字节
Extra列
Using index
- mysql执行计划explain结果里的key有使用索引,如果select后面查询的字段都可以从这个索引的树中获取,这种情况一般可以说是用到了覆盖索引,extra里一般都有using index;
- 覆盖索引一般针对的是辅助索引,整个查询结果只通过辅助索引就能拿到结果,不需要通过辅助索引树找到主键,再通过主键去主键索引树里获取其它字段值
Using where
Using index condition
Using temporary
Using filesort
索引最佳实践
全值匹配
最左前缀法则
创建索引,根据最左前缀,将等号查询的范围越大的越往左边放置,范围查询的往右边放置
不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
因为索引树上没有进行了这些操作的索引,mysql没有这些优化
存储引擎不能使用索引中范围条件右边的列
尽量使用覆盖索引(只访问索引的查询(索引列包含查询列)),减少select*语句
like以通配符开头('$abc...')mysql索引失效会变成全表扫描操作
常见sql深入优化
Order by与Group by优化
2、order by满足两种情况 会使用Using index。
1) order by语句 使用索引最左前列。
2) 使用 where子句与order by子句 条件列组合满足索引最左前列。
3、尽量 在索引列上完成排序, 遵循索引建立(索引创建的顺序) 时的最左前缀法则。
4、如果 order by的条件 不在索引列上,就 会产生Using filesort。
5、 能用覆盖索引 尽量用覆盖索引
6、 group by与order by很类似,其实质是 先排序后分组, 遵照索引创建顺序的最左前缀法则。对于group by的优化如果 不需要排序的可以加上 order by null禁止排序。注意, where高于having,能写在 where中的限定条件就不要去having限定了。
Using filesort文件排序原理详解
单路排序
是一次性取出满足条件行的所有字段,然后在sort buffer中进行排序;用trace工具可以看到sort_mode信息里显示< sort_key, additional_fields >或者< sort_key, packed_additional_fields >
双路排序(又叫回表排序模式)
- 如果 字段的总长度小于max_length_for_sort_data ,那么使用 单路排序模式;
- 如果 字段的总长度大于max_length_for_sort_data ,那么使用 双路排序模式。
索引设计原则
代码先行,索引后上
联合索引尽量覆盖条件
不要在小基数字段上建立索引
长字符串我们可以采用前缀索引
但是假如你要是 order by name,那么此时你的name因为在索引树里 仅仅包含了前20个字符,所以这个 排序是没法用上索引的, group by也是同理。
where与order by冲突时优先where
因为大多数情况基于索引进行where筛选往往可 以最快速度筛选出你要的 少部分数据,然后 做排序的成本可能会小很多
分页查询优化
Join关联查询优化
嵌套循环连接 Nested-Loop Join(NLJ) 算法
- 优化器一般会优先选择小表做驱动表,用where条件过滤完驱动表,然后再跟被驱动表做关联查询。所以使用 inner join 时,排在前面的表并不一定就是驱动表。
- 当使用left join时,左表是驱动表,右表是被驱动表,当使用right join时,右表是驱动表,左表是被驱动表,当使用join时,mysql会选择数据量比较小的表作为驱动表,大表作为被驱动表。
- 使用了 NLJ算法。一般 join 语句中,如果执行计划 Extra 中未出现 Using join buffer 则表示使用的 join 算法是 NLJ。
基于块的嵌套循环连接 Block Nested-Loop Join(BNL)算法
对于关联sql的优化
关联字段加索引让mysql做join操作时尽量选择NLJ算法,驱动表因为需要全部查询出来,所以 过滤的条件也尽量要走索引, 避免全表扫描,总之, 能走索引的过滤条件尽量都走索引小表驱动大表写多表连接sql时如果 明确知道哪张表是小表可以 用straight_join写法固定连接驱动方式, 省去mysql优化器自己 判断的时间两个表按照各自的条件过滤, 过滤完成之后, 计算参与 join 的各个字段的总数据量, 数据量小的那个表,就是“小表”,应该 作为驱动表。
in和exsits优化
in
当B表的数据集小于A表的数据集时,in优于exists
select * from A where id in (select id from B)
#等价于:
for(select id from B){
select * from A where A.id = B.id
}
exists
当A表的数据集小于B表的数据集时,exists优于in
select * from A where exists (select 1 from B where B.id = A.id)
#等价于:
for(select * from A){
select * from B where B.id = A.id
}
count(*)查询优化
字段有索引
字段无索引
count(1)
跟count(字段)执行过程类似,不过count(1)不需要取出字段统计,就用常量1做统计,count(字段)还需要取出字段,所以理论上count(1)比count(字段)会快一点。
count(*)
mysql并不会把全部字段取出来,而是专门做了优化,不取值,按行累加,效率很高,所以不需要用count(列名)或count(常量)来替代 count(*)。
对于count(id),mysql最终选择辅助索引而不是主键聚集索引,因为二级索引相对主键索引存储数据更少,检索性能应该更高,mysql内部做了点优化(应该是在5.7版本才优化)
常见优化方法
查询mysql自己维护的总行数
MySQL数据类型选择
(2) 确定具体的类型: 有无符号、取值范围、变长定长等。
数值类型
- 如果整形数据没有负数,如ID号,建议指定为UNSIGNED无符号类型,容量可以扩大一倍。
- 建议使用TINYINT代替ENUM、BITENUM、SET。
- 避免使用整数的显示宽度(参看INT显示宽度),也就是说,不要用INT(10)类似的方法指定字段显示宽度,直接用INT。
- DECIMAL最适合保存准确度要求高,而且用于计算的数据,比如价格。但是在使用DECIMAL类型的时候,注意长度设置。
- 建议使用整形类型来运算和存储实数,方法是,实数乘以相应的倍数后再操作。
- 整数通常是最佳的数据类型,因为它速度快,并且能使用AUTO_INCREMENT。
INT显示宽度
字段的类型长度TINYINT(2)、INT(11)不会影响数据的插入,只会在使用ZEROFILL时有用,让查询结果前填充0。
日期和时间
- MySQL能存储的最小时间粒度为秒。
- 建议用DATE数据类型来保存日期。MySQL中默认的日期格式是yyyy-mm-dd。
- 用MySQL的内建类型DATE、TIME、DATETIME来存储时间,而不是使用字符串。
- 当数据格式为TIMESTAMP和DATETIME时,可以用CURRENT_TIMESTAMP作为默认(MySQL5.6以后),MySQL会自动返回记录插入的确切时间。
- TIMESTAMP是UTC时间戳,与时区相关。
- DATETIME的存储格式是一个YYYYMMDD HH:MM:SS的整数,与时区无关,你存了什么,读出来就是什么。
- 除非有特殊需求,一般的公司建议使用TIMESTAMP,它比DATETIME更节约空间,但是像阿里这样的公司一般会用DATETIME,因为不用考虑TIMESTAMP将来的时间上限问题。
- 有时人们把Unix的时间戳保存为整数值,但是这通常没有任何好处,这种格式处理起来不太方便,我们并不推荐它。
字符串
- 字符串的长度相差较大用VARCHAR;字符串短,且所有值都接近一个长度用CHAR。
- CHAR和VARCHAR适用于包括人名、邮政编码、电话号码和不超过255个字符长度的任意字母数字组合。那些要用来计算的数字不要用VARCHAR类型保存,因为可能会导致一些与计算相关的问题。换句话说,可能影响到计算的准确性和完整性。
- 尽量少用BLOB和TEXT,如果实在要用可以考虑将BLOB和TEXT字段单独存一张表,用id关联。聚簇索引,这个字段字节数很大都放在一个表里面影响效率
- BLOB系列存储二进制字符串,与字符集无关。TEXT系列存储非二进制字符串,与字符集相关。
- BLOB和TEXT都不能有默认值。
utf8字符集
utf8
一个数字或字母占1个字节,一个汉字占3个字节
utf8mb4
4个子节,可以用于存放表情
MySQL排序规则
utf8mb4_general_ci