数据库设计应该考虑的问题
为什么要使用索引
- 快速查询数据
索引的数据结构
- 建立二叉查找树进行二分查找
- 建立BTree结构进行查找
- 建立B+Tree结构进行查找
- 建立Hash结构进行查找
BTree
- 根节点至少包括两个孩子
- 树中每个节点最多含有m个孩子
- 除了根节点和叶子节点,其它每个节点至少有ceil(m / 2)个孩子
- 所有叶子节点都位于同一层
B+Tree
- 非叶子节点的子树指针与关键字个数相同
- 非叶子节点的子树指针p[i],指向关键字值[k[i],k[i+1])的子树
- 非叶子节点仅用来索引,数据都保存在叶子节点中
- 所有叶子节点均有一个链指针指向下一个叶子节点
B+Tree更适合用来做存储索引
- B+树的磁盘读写代价更低(数据只存储在叶子节点,可以减少磁盘IO)
- B+树的查询效率更稳定
- B+树更加有利于对数据库的扫描
Hash索引
查询速度理论上比B+Tree快
缺点:
- 仅仅能满足
=
,in
,不能使用范围查询 - 无法被用来避免数据的排序操作
- 不能利用部分索引键查询
- 遇到大量的Hash碰撞,性能不一定比BTree高
密集索引和稀疏索引的区别
密集索引 叶子节点保存的不仅仅是键值,还有保存了同一行中其它列的信息。密集索引还决定了表的物理排序顺序。一个表只能有一个物理排序顺序,所以一个表只能有一个密集索引。
稀疏索引 叶子节点仅保存了键位信息和主键信息,需要通过主键索引再查询到数据。
InnoDB
- 若一个主键被定义,该主键则作为密集索引
- 若没有主键被定义,该表的第一个唯一非空索引则作为密集索引
- 若不满足以上条件,Innodb内部会生成一个隐藏的主键(密集索引)
- 非主键索引存储相关键位和其对应的主键值,包含两次查找
如何定位并优化慢查询sql
- 根据慢日志定位慢查询SQL
- 使用explain等工具分析SQL
- 尽量修改SQL或者尽量让SQL走索引
查询跟慢日志相关的配置信息(基于mysql 8.0.x)
SHOW VARIABLES LIKE '%query%'
显示结果如下
Variable_name Value
---------------------------- -------------------------------------------------
binlog_rows_query_log_events OFF
ft_query_expansion_limit 20
have_query_cache NO
long_query_time 1.000000
query_alloc_block_size 8192
query_prealloc_size 8192
slow_query_log ON
slow_query_log_file /var/lib/mysql/izwz99rnmfr0cdntpgsfr9z-slow.log
slow_query_log
为ON表示已经开启了慢日志
slow_query_log_file
为慢日志文件路径
long_query_time
表示多长时间被认为是慢SQL,单位微妙(us) 1.000000us = 1s
打开慢查询日志
如果slow_query_log为OFF则未打开慢日志,如下打开可以打开
SET GLOBAL slow_query_log = ON
修改被认为是慢查询的耗时
SET GLOBAL long_query_time = 1; #单位1s
SET GLOBAL long_query_time = 0.8; #单位0.8s = 800ms
修改过后客户端需要重连,才能生效,并查看修改后的结果,并且mysql重启之后,会恢复默认认值。可以通过修改mysql 配置文件永久生效
查询被统计的慢查询次数
SHOW STATUS LIKE '%slow_queries%'
只是显示当前连接的慢查询次数,重连之后恢复为 0
在mysql服务器查看慢日志文件
# Time: 2021-02-22T13:39:46.688722Z
# User@Host: root[root] @ [139.226.12.65] Id: 201
# Query_time: 1.006213 Lock_time: 0.195229 Rows_sent: 0 Rows_examined: 0
use lwl_registry;
SET timestamp=1614001185;
select * from order_info where pwd = '122'
Query_time为本次查询耗时
通过explain
分析sql语句
通过explain可以查看sql有没有利用到索引
EXPLAIN SELECT * FROM order_info WHERE username = '122'
显示结果如下
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
------ ----------- ---------- ---------- ------ ------------- ------ ------- ------ ------ -------- -------------
1 SIMPLE order_info (NULL) ALL (NULL) (NULL) (NULL) (NULL) 360772 10.00 Using where
type
:表示查找到数据行的方式,性能排序从高到低依次为:
system
> const
> eq_ref
> ref
> fulltext
> ref_or_null
> index_merge
> unique_subquery
> index_subquery
> range
> index
> all
index
和 all
表示本次查询走的是全表扫描,如果慢查询语句中EXPLAIN结果是这两个值,则需要考虑优化。
Extra
:如果出现如下2项的值,则意味着mysql根本没有使用索引,需要考虑优化
Extra值 | 说明 |
---|---|
Using filesort | 表示MySQL会对结果使用一个外部索引排序,而不是从表里按索引依序读到相关内容,可能在内存或磁盘上进行排序。 MySQL中无法利用索引完成的排序操作称为文件排序 |
Using temporary | 表示mysq在对查询结果排序时使用临时表。常见于排序order by 和分组查询group by 。 |
常见优化方案:
- 如果业务允许,可以通过使用已有的索引字段查询。
- 适当的添加索引
alter table order_info add index idx_name(username)
- 利用索引字段排序
- 索引总结
MySQL只对以下操作符才使用索引,<
,<=
,=
,>=
,between and
,like 非%开头
使用索引,in
可能走索引可能不走,!=
不走索引,理论上每张表可创建16个索引。 - MySQL会对
允许
null列也加上索引,但只会对
is null``的查询条件生效
问题:假如InnoDB中表中有id主键索引,并且有个不为空的唯一索引sessionId,那么通过count(id)统计数据条数时,会利用到哪个索引字段?
EXPLAIN SELECT COUNT(id) FROM `client`
答案:会利用不为空的唯一索引sessionId,结果如下:
原因: 查询优化器会选用最优的索引,因为InnoDB中主键使用的聚集索引,叶子节点中含有其它列的数据,需要读取更多的数据。而sessionId不为空,且是非聚集索引,只含有索引字段和主键,数据量小,读取快。
强制走索引
如果有where条件,则会利用条件中的索引
SELECT COUNT(id) FROM `client` WHERE id = 39 #走主键索引
SELECT COUNT(id) FROM `client` WHERE session_id = 1427325133344736216 #走唯一索引
联合(组合)索引最左匹配原则的原因
- 组合索引,最左匹配原则,mysql会一直向右匹配,直到遇到范围查询(
>
、<
、between
、like
)就停止匹配。例如组合索引(a,b,c,d) 查询条件where a=3 and b=4 and c>5 and d = 6
,则d用不到索引。 =
和in
可以乱序,例如组合索引(a,b,c) 查询条件where a=3 and c=5 and b=4
即使查询条件不是abc的顺序,但是经过查询优化器优化后都可以走索引. 但是where c=5 and b=4
不满足最左匹配,无法走索引
最左匹配原则的原因: 因为第二个字段的排序是在第一个字段基础上排序,单独以第二个字段来看是无序的。
索引是建立越多越好么?
- 数据量小的表不需要建立索引,建立索引会增加额外的索引开销
- 数据变更需要维护索引,因此更多的索引意味着更多的维护成本
- 更多的索引意味着需要很多的空间