MySQL原理与优化
MySQL中的B+ Tree
在MySQL的InnoDB引擎创建的表,使用的B+ Tree的结构存储的数据,并对B+ Tree进行了优化。
B+ Tree只有单项指针, MySQL在这个基础上做了双向指针,这是支持范围查找的关键。
一般建表后,数据通过B+ Tree的数据结构存储,叶子节存储的是所有数据,那么非叶子节点中存储的是谁?
当设置主键后,非叶子节点存储的就是主键,通过这样的结构将数据存储在磁盘中,所以会自动生成主键索引。
为什么B+ Tree查询快?
MySQL默认B+ Tree的节点大小为16KB。
以主键索引举例,主键类型为bigint,默认占用8字节。
当做数据的key存储在非叶子节点中,因为要找到叶子节点中的数据,所以key需要自己下层索引的磁盘地址,这个占用6字节。
那么一个节点最大索引(key)数量为16KB/8+6=1170
一个h3(高度3)的数据,可以存储1170*1170*16=2KW+的数据。
因为高度为3,所以2KW数据最终只执行了3次磁盘IO交互就完成了查找。
在高版本的MySQL中,在MySQL启动时,会将非叶子节点放到内存中,这样通过一次磁盘交互就可以找到数据,所以查询很快。
索引的存储
索引会占用磁盘空间,因为所有的索引都是一个采用B+ Tree结构的数据文件,存储在磁盘中。
区别在于主键索引(聚簇索引)在B+ Tree的叶子节点上有数据。
其他索引(非聚簇索引)的叶子节点上存储的是主键。
当使用其他索引会先找到叶子节点上的主键,然后通过主键索引找到数据。
主键类型的选择
主键类型选择int,bigint与varchar。
bigint(推荐):在B+ Tree结构的遍历查找中,会进行大小比较,整型比较大小快,支持的数据量更大。
int:与bigint优势大体相同,但是支持的数据量不够大,并且使用一些工具方法生成ID是位数也不够。
varchar(不推荐):在B+ Tree结构的遍历查找中,比较大小会进行转hash码逐位比较,性能比整型差,占用空间也大。
主键属性可以选择自增,UUID,雪花等。
自增:在B+ Tree结构中是十分友好的增加数据方式,只会往树的右边插入数据,很少会出现触发树平衡的情况。
如果需要分表分库,也会有自增ID重复的情况。
同时也存在一定程度上的业务问题,比如用户知道ID为自增后,通过循环调用接口的方法获取所有数据。
UUID(不推荐):UUID的值是随机的,可能会往B+ Tree的左边插入数据,如果左边节点中的key满了,那么会导致整个树进行分裂来重新平衡,这很消耗性能,而UUID可能会平凡触发这个操作。
雪花等其它方法(推荐):以雪花算法为例,它是趋势递增,新的主键小于之前的概率很小,不会频繁触发树平衡机制。
聚簇索引与非聚簇索引
聚簇索引
Mysql自动将采用了INNODB存储引擎表中主键建立索引,这个索引就是聚簇索引。
如果当前表中没有主键,MySql将会选择一个添加唯一性约束的字段作为聚簇索引。
如果当前表中既没有主键字段,也没有添加了唯一性约束字段MySql将随机选取一 个字段作为聚簇索引。
在采用INNODB存储引擎的表文件中,必然会存在一个聚簇索引。
在采用INNODB存储引擎的表文件中,只能有一个聚簇索引。
在表文件中其他字段上建立的索引都是非聚簇索引。
非聚簇索引
是由开发人员自行创建.
对于采用了INNODB存储引擎表,除了一个聚簇索引之外,其他字段上创建的索引都是非聚簇索引
在采用MyIsam存储引擎的表中,创建的所有索引都是非聚簇索引
区别
聚簇索引非叶子节点存储着主键,叶子节点存储着数据。
非聚簇索引非叶子节点存储key,叶子节点存储着主键。找到主键后要通过聚簇索引才可以找到数据。
MySQL事务的四大特性ACID及其原理
ACID四大特性
原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么不执行。
一致性(Consistency):在事务开始之前和事务结束之后,数据不被破坏,假如A账户给B账户转10块钱,不管成功与否,A和B的总金额是不变的。
隔离性(Isolation):多个事务并发访问时,事务之间是相互隔离的,即一个事务不影响其他事务运行效果。
持久性(Durability):表示事务完成以后,该事务对数据库所作的操作更改,将持久地保存在数据库之中。
ACID实现原理
事务的隔离性是通过数据库锁以及MVCC机制实现的。
事务的一致性由undo log来保证:undo log是逻辑日志,记录了事务的Insert、update、delete操作,回滚的时候做相反的delete、update、insert操作来恢复数据。
事务的原子性和持久性由redo log来保证:redo log被称作重做日志,是物理日志,事务提交的时候,必须先将事务的所有日志写入redo log持久化,到事务的提交操作才算完成。
事务的各个隔离级别都是如何实现的
读未提交
采用的是读不加锁原理
事务读不加所,不阻塞其他事务的读和写
事务写阻塞其他事务写,单不阻塞其他事务读
读已提交&可重复读
READ COMMITTED: 每次读取数据千都生成一个ReadView
REPEATABLE READ: 在事务里每次读取数据时生成一个ReadView
串行化
串行化的实现采用的时读写都加锁的原理
串行化的情况下,对于同一行事务,写会家写锁,读会加读锁。当出现读写锁冲突的时候,后访问的事务必须等待前一个事务执行完成,才能继续执行。
MySQL查询语句中七个查询命令的特性
查询的优先级(谁先执行)
FROM > WHERE > GROUP BY > HAVING > SELECT > ORDER BY > LIMIT
FROM
将硬盘上的表文件加载到内存中,生成一个全新的临时表。
WHERE
遍历FROM生成的临时表,根据条件进行过滤,并保存到一个新的临时表中,因为是逐行判断,所以无法使用聚合函数
GROUP BY
对临时表的数据先排序后分组,如果是多字段,那么第二字段开始操作的是上一个字段生成的临时表。
如果SELECT操作的临时表的GROUP BY提供的,那么会遍历所有分组字段的临时表的第一行内容。
HAVING
将GROUP BY生成的临时表中不满足条件的数据进行删除,并不会生成新的临时表。
这是操作的是一整个临时表,所以可以使用聚合函数。
SELECT
SELECT操作的临时表由FROM或WHERE提供的,那么会根据指定字段读取内容生成新的临时表。
如果由GROUP BY或HAVING提供的,那么会遍历GROUP BY生成的每一个临时,只读取第一行内容。
ORDER BY
专门针对SELECT生成的临时表中的数据进行排序,排序后的数据组成一个新的临时表。
LIMIT
对临时表的数据进行截取,并生成新的临时表。
七个命令中,只有HAVING不会生成临时表,其它都会。
七个命令中,只有FROM是从表文件从加载到内存,其他命令操作的都是上一个命令生成的临时表。
在当前命令执行完后,MySQL会销毁上一个命令生成的临时表。
多字段分组时,从第二个字段开始,操作的是上一个字段生成的临时表,所以多字段分组会生成多张临时表。
七个查询命令,那些命令执行的效率过慢
执行最慢的的是GROUP BY
原因:group by执行时间首先将临时表中的数据进行排序(排序等于全表遍历),然后再进行分组
order by
原因:需要对select生成的临时表中的数据进行一次排序(全表遍历),然后将拍好顺序的数据行组成一个全新的临时表
where
原因:需要对表中所有的数据行进行遍历,如果当前的临时表中的数据行过多,就会导致where执行效率非常慢
limit
原因:如果指定行数过大,就会导致查询速度慢。比如指定截取到20000行,它会从第0行开始找到20000行,指定的太大会遍历的数据过多,导致查询慢。
多表查询
多表查询没有指定表文件的加载顺序,也会导致查询速度过慢,也就是常说的小表驱动大表
MySQL查询慢的原因
用户量访问激增,访问量过大,使用mysql集群+负载均衡来处理
某些表数据过大,针对某些表查询慢,解决方案为查询语句优化
判断查询语句是否需要优化依赖工具
慢查询记录
explain执行计划
相关日志文件(查看用户频繁执行的SQL语句是什么,方便创建多字段索引)
Explain执行计划详解
id属性:
【作用】: 通过id属性支出当前查询语句中相关表文件的加载顺序
【含义】:
情况1:如果被加载的表文件的ID属性内容是相同的,则根据自上而下的顺序进行加载 的.
情况2: 如果被加载的表文件的ID属性内容是不相同的。则ID较大的表,就先被加载。 ID较小的表,则后被加载.
情况3:如果被夹在的表文件的ID属性内容既有相同有存在不同时。则ID较大的表,就 现被加载。ID相同的表则按照自上而下顺序进行加载。
select_type属性:
【作用】: 对当前查询语句中的查询类型进行判断
【含义】:
1) Simple
表示当前查询语句是一个简单查询语句,不包含子查询,不包含联合查询,不半酣连接查询
2) Primary
如果执行的是一个包含子查询或者联合查询
Primary 指向的外部查询语句或者是联合查询的第一个子查询语句
3) DEPENDENT SUBQUERY
表示当前查询语句是一个子查询,并且执行条件依赖与外部查询提供的条件
4) SUBQUERY
表示当前查询是一个子查询,并且这个子查询再执行时不需要得到外部查询的帮助
type属性(重要):
【作用】: 表示当前查询语句是否用到索引,索引的使用方式,当前查询语句的评价级别
【含义】:
以下按照最差级别到最好级别的顺序排列
1)all:全表扫遍,没有任何索引可以使用时,这是最差的情况,应该避免
2)index:定位数据行时,没有使用到索引(全表扫描),但是在已经定位的数据行中 读取字段时,却是使用索引,也就是说select命令前的其他命令没有触发索引, select却命中索引,这时才根据索引读取数据,执行效率低。
3)range:定位数据行,使用到索引。使用区间定位方式(where age < 23),这种状态 实际上是一种不稳定的状态,如果定位的数据行操作了表中总行数的30% 以上,就会导致mysql放弃使用索引定位。(开发中至少要达到这个级别)
4)eq_ref:关联查询时,根据唯一非空索引进行查询的情况
5)ref:定位数据行,使用到索引,采用直等方式(where name = zhangsan)
同时满足定位的条件的数据行只有一行。
6)const:定位数据行,使用到索引,采用主键索引来进行直等的方式(where id = 3)
7)system:操作表只有一行数据,并且数据内容是固定不变的。(实际开发中不存在)
8)null:无需访问表或者索引,比如获取一个索引列的最大值或最小值
******** SQL优化时,索引级别最低保证达到range级别
索引级别最高达到ref
用户查询用,一般不会使用主键字段如库表中的ID来进行查询,而是非主键字段,所以达到ref即可,而不是const级别
possible_keys属性:
【作用】: 表示当前查询语句执行时可能使用到的索引有那些。
key属性:
【作用】: 表示当前查询语句执行时真实使用到的索引,如果这个字段为null,则有两种可能,一是当前表中没有索引,二是当前表中有索引但是失效了。
possible_keys显示可能使用的索引有多个,mysql会选择性能最高的索引。
key_len属性:
【作用】: 如果本次查询使用了索引,则key_len内容不为空。
这里的数字表示当前采用索引字段的数据长度,这个值越大,定位数据更加精准。
索引那些情况下会失效
查询条件包含or,可能导致索引失效
如果字段类型时字符串,where时一定用引号括起来,否则会因为隐式类型转换,索引失效
索引字段模糊搜索,只支持前置条件模糊搜索
联合索引,查询的条件列不是联合索引的第一列,索引失效
在索引列上使用Mysql内置函数,索引失效
对索引列运算,如 +、-、*、/ ,索引失效
索引字段上使用 !=或者 < >,not in时,可能会导致索引失效
索引上字段使用is null ,is not null可能会导致索引失效
左联机查询或右连接查询查询关键的字段编码格式不一样,可能导致索引失效
Mysql优化器估计使用全表扫描比使用索引快,则不使用索引。