整体结构图
和其它数据库相比,MySQL 有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上,插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。
从架构图上看我们可以把MySQL大致分成4层,分别是连接层,服务层,引擎层和存储层
连接层:最上层是一些客户端和连接服务,包含本地 sock 通信和大多数基于客户端/服务端工具实现的类似于 tcp/ip 的 通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证 安全接入的客户端提供线程。同样在该层上可以实现基于 SSL 的安全链接。服务器也会为安全接入的每个客户端验 证它所具有的操作权限。
服务层:
引擎层:存储引擎层,存储引擎真正的负责了 MySQL 中数据的存储和提取,服务器通过 API 与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取。
存储层:数据存储层,主要是将数据存储在运行于裸设备的文件系统之上,并完成与存储引擎的交互。
MyISAM和InnoDB
和引擎相关的SQL
show engines :查看所有的数据库引擎
show variables like '%storage_engine%' :查看默认的数据库引擎
索引优化分析
作为一名后端程序员,写出性能优良的SQL是我们最终的目的。我们都知道,影响性能的往往都是一些查询语句,当查询的数据量达到一定级别时,有的时候我们能明显的感觉到系统变慢,所以接下来要讲的优化都是针对查询语句而言的。MySQL为我们提供了一个优化的方向,那就是索引,索引优化是一个后端程序员必须要掌握的技能点。
索引
MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。我们可以简单理解为索引就是排好序的快速查找数据结构。对于数据库系统而言,在数据之外,还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据, 这样就可以在这些数据结构上实现高级查找算法。下图就是一种可能的索引方式示例:
左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址。为了加快 Col2 的查找,可以维护一个 右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快速的检索出符合条件的记录。
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上。
索引原理
【初始化介绍】
一颗 b 树,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块 1 包含数据项 17 和 35,包含指针 P1、P2、P3;P1 表示小于 17 的磁盘块,P2 表示在 17 和 35 之间的磁盘块,P3 表示大于 35 的磁盘块。
真实的数据存在于叶子节点即 3、5、9、10、13、15、28、29、36、60、75、79、90、99。非叶子节点不存储真实的数据,只存储指引搜索方向的数据项,如 17、35 并不真实存在于数据表中。
【查找过程】
如果要查找数据项 29,那么首先会把磁盘块 1 由磁盘加载到内存,此时发生1次 IO,在内存中用二分查找确定 29 在 17 和 35 之间,锁定磁盘块 1 的 P2 指针,通过磁盘块 1 的 P2 指针的磁盘地址把磁盘块 3 由磁盘加载到内存,发生第2次 IO,29 在 26 和 30 之间,锁定磁盘块 3 的 P2 指 针,通过指针加载磁盘块 8 到内存,发生第3次 IO,同时内存中做二分查找找到 29,结束查询,总计3次 IO。
真实的情况是,3 层的 b+树可以表示上百万的数据,如果上百万的数据查找只需要3次 IO,性能提高将是巨大的, 如果没有索引,每个数据项都要发生一次 IO,那么总共需要百万次的 IO,显然成本非常非常高。
上面是通过Btree(B-tree)的方式实现的索引,接下来我们再看一种B+tree的数据结构实现的索引方式
B+Tree 与 B-Tree 的区别
1.B-树的关键字和记录是放在一起的,叶子节点可以看作外部节点,不包含任何信息;B+树的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。
2.在 B-树中,越靠近根节点的记录查找时间越快,只要找到关键字即可确定记录的存在;而 B+树中每个记录的查找时间基本是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字。从这个角度看 B- 树的性能好像要比 B+树好,而在实际应用中却是 B+树的性能要好些。因为 B+树的非叶子节点不存放实际的数据, 这样每个节点可容纳的元素个数比 B-树多,树高比 B-树小,这样带来的好处是减少磁盘访问次数。尽管 B+树找到 一个记录所需的比较次数要比 B-树多,但是一次磁盘访问的时间相当于成百上千次内存比较的时间,因此实际中 B+树的性能可能还会好些,而且 B+树的叶子节点使用指针连接在一起,方便顺序遍历(例如查看一个目录下的所有 文件,一个表中的所有记录等),这也是很多数据库和文件系统使用 B+树的缘故。
思考:为什么说 B+树比 B-树更适合实际应用中操作系统的文件索引和数据库索引?
1) B+树的磁盘读写代价更低;B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对 B 树更小。如果把所有同一内部结点 的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就 越多。相对来说 IO 读写次数也就降低了。
2) B+树的查询效率更加稳定;由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须 走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
索引的优势和劣势
优势:
1.提高数据检索的效率,降低数据库的IO成本。
2.通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗。
劣势:
1.虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为 更新所带来的键值变化后的索引信息。
2.实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间 的。
MySQL索引
分类
单值索引:一个索引只包含单个列,一个表可以有多个单值索引
唯一索引:索引列的值必须唯一,但允许有空值
主键索引:设定为主键后数据库会自动建立索引
复合索引:一个索引包含多个列
语法
索引的创建时机
适合创建索引的情况
主键自动建立唯一索引;
频繁作为查询条件的字段应该创建索引
查询中与其它表关联的字段,外键关系建立索引
单键/组合索引的选择问题, 组合索引性价比更高
查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度 查询中统计或者分组字段
不适合创建索引的情况
表记录太少
经常增删改的表或者字段
Where条件里用不到的字段不创建索引
过滤性不好的不适合建索引
Explain性能分析
语法:Explain + SQL语句
返回结果信息如下所示:
关于这10个参数的具体解释如下:
id:select 查询的序列号,包含一组数字,表示查询中执行 select 子句或操作表的顺序。有三种情况
id 相同,执行顺序由上至下
id不同,id 不同,如果是子查询,id 的序号会递增,id 值越大优先级越高,越先被执行
id有相同也有不同,id 如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id 值越大,优先级越高,越先执行衍生 = DERIVED
关注点:id 号每个号码,表示一趟独立的查询。一个 sql 的查询趟数越少越好。
select_type:代表查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询。
table:表名
type:查询的访问类型。是较为重要的一个性能指标,结果值从最好到最坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index >all
一般来说,得保证查询至少达到 range 级别,最好能达到 ref。
possible_keys:显示此次查询中可能应用到的在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。
key:实际使用的索引。如果为NULL,则没有使用索引。
key_len:表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。 key_len 字段能够帮你检查是否充分的 利用上了索引。ken_len 越长,说明索引使用的越充分。
ref:显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。
rows:rows 列显示 MySQL 认为它执行查询时必须检查的行数。越少越好!
Extra:其他的额外重要的信息。
Using filesort:说明 mysql 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL 中无法利用 索引 完成的排序操作称为“文件排序”。
Using temporary:使了用临时表保存中间结果,MySQL 在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by
Using index:Using index 代表表示相应的 select 操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率 不错! 如果同时出现 using where,表明索引被用来执行索引键值的查找;如果没有同时出现 using where,表 明索引只是用来读取数据而非利用索引执行查找。
Using where:表明使用了 where 过滤。
Using join buffer:使用了连接缓存。
impossible where:where 子句的值总是 false,不能用来获取任何元组。
当extra出现了Using filesort和Using temporary就表明我们应该对sql进行优化了,最好的情况是出现using index
单表索引优化原则
1.全职匹配我最爱指的是,查询的字段按照顺序在索引中都可以匹配到!
2.最佳左前缀法则:过滤条件要使用索引必须按照索引建立时的顺序,依次满足,一旦跳过某个字段,索引后面的字段无法被用
3.不要在索引列上做任何计算:不在索引列上做任何操作(计算、函数、(自动 or 手动)类型转换),会导致索引失效而转向全表扫描。
4.不要在查询列上使用了函数:等号左边无计算
5.查询列上不要做类型转换:等号右边无转换!
6.索引列上不能有范围查询:将可能做范围查询的字段的索引顺序放在最后,不然它后面的都会失效
7.尽量使用覆盖索引:查询列和索引列保持一致,少用select *
8.使用不等于(!= 或者<>)的时候会导致索引失效
9.字段的 is not null 和 is null:is not null 用不到索引,is null 可以用到索引。
10.like 的前后模糊匹配:前缀不能出现模糊匹配,即like后面不要出%,如果一定要出,则尽量使用索引覆盖
11.减少使用or:使用 union all 或者 union 来替代:
记忆口诀
全职匹配我最爱,最左前缀要遵守; 带头大哥不能死,中间兄弟不能断; 索引列上少计算,范围之后全失效; LIKE 百分写最右,覆盖索引不写*; 不等空值还有 OR,索引影响要注意; VAR 引号不可丢,SQL 优化有诀窍。
关联查询优化原则
在优化关联查询时,只有在被驱动表上建立索引才有效!
left join 时,左侧的为驱动表,右侧为被驱动表!
inner join 时,mysql 会自己帮你把小结果集的表选为驱动表。straight_join 效果和 inner join 一样,但是会强制将左侧作为驱动表!
子查询尽量不要放在被驱动表,有可能使用不到索引;left join时,尽量让实体表作为被驱动表。能够直接多表关联的尽量直接关联,不用子查询!
排序和分组优化原则
order by优化
1.无过滤,不索引。where,limt 都相当于一种过滤条件,所以才能使用上索引!
2.顺序错,必排序:where 两侧列的顺序可以变换,效果相同,但是 order by 列的顺序不能随便变换!
3.方向反,必排序:排序的字段有多个时,要么都遵守升序要么都遵守降序,否则必然Using filesort
4.当范围条件和 group by 或者 order by 的字段出现二选一时 ,优先观察条件字段的过滤数量,如果过滤的 数据足够多,而需要排 序的数据并不多时,优先把索引放在范围字段上。反之,亦然。
截取查询分析
当系统运行缓慢的时候,我们想知道当前哪些SQL查询缓慢,这个时候我们就需要借助一些工具找出那些我们认为慢的SQL进行分析优化
慢查询日志
(1)MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。
(2)具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。long_query_time的默认值为10,意思是运行10秒以上的语句。
(3)由他来查看哪些SQL超出了我们的最大忍耐时间值,比如一条sql执行超过5秒钟,我们就算慢SQL,希望能收集超过5秒的sql,结合之前explain进行全面分析。
使用方式
默认情况下,MySQL 数据库没有开启慢查询日志,需要我们手动来设置这个参数。
当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。 慢查询日志支持将日志记录写入文件。
开启设置
除了这种方式,我们还可以通过其他方式来进行SQL分析,比如show profile
show profile
开启profile
使用profile
MySQL的锁机制
分类
从对数据操作的类型分:读锁(read)和写锁(write),读锁为共享锁,针对同一份数据,多个读操作可以同时进行而不会互相影响写 锁🈶️叫排他锁,当前写操作没有完成前,它会阻断其他写锁和读锁。
从对数据的操作粒度分:表锁和行锁;表锁偏读,行锁偏写
语法
lock table 表名字 read(write)
间隙锁的危害
当一个事务在操作某一个范围内的数据时,未提交,那么此时当有其他事务操作的数据也在该范围时会陷入阻塞状态
索引失效行锁变表锁
如何锁定某一行
select * from test where id=1 for update
查看数据库当前锁的状态
show status like 'innodb_row_lock%'