MySQL 架构

连接层:最上层是一些客户端和连接服务。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。

服务层:第二层服务层,主要完成大部分的核心服务功能, 包括查询解析、分析、优化、缓存、以及所有的内置函数,所有跨存储引擎的功能也都在这一层实现,包括触发器、存储过程、视图等

引擎层:第三层存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取

存储层:第四层为数据存储层,主要是将数据存储在运行于该设备的文件系统之上,并完成与存储引擎的交互

联想提问问题:画出 MySQL 架构图? or MySQL 的查询流程具体是? or 一条SQL语句在MySQL中如何执行的?

客户端请求 —> 连接器(验证用户身份,给予权限) —> 查询缓存(存在缓存则直接返回,不存在则执行后续操作) —> 分析器(对SQL进行词法分析和语法分析操作) —> 优化器(主要对执行的sql优化选择最优的执行方案方法) —> 执行器(执行时会先看用户是否有执行权限,有才去使用这个引擎提供的接口) —> 去引擎层获取数据返回(如果开启查询缓存则会缓存查询结果)

Mysql 架构图

MySQL 多表连接执行顺序

MySQL 执行顺序

多表连接采用『驱动表』策略:

何谓驱动表,指多表关联查询时,第一个被处理的表,亦可称之为基表,然后再使用此表的记录去关联其他表。

驱动表的选择遵循一个原则:在对最终结果集没影响的前提下,优先选择结果集最少的那张表作为驱动表。

连表顺序,不是两两联合之后,再去联合第三张表,而是驱动表的一条记录穿到底,匹配完所有关连表之后,再取驱动表的下一条记录重复连表操作。

多表连接并过滤条件完毕后,再执行 order by 、limit 等操作。

Mysql 执行顺序

sql执行顺序

存储引擎

InnoDB 对比 MyISAM

对比项 MyISAM InnoDB
主外键 不支持 支持
事务 不支持 支持
行表锁 表锁,即使操作一条记录也会锁住整个表,不适合高并发的操作 行锁,操作时只锁某一行,不对其它行有影响,适合高并发的操作
缓存 只缓存索引,不缓存真实数据 不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响
表空间
关注点 性能 事务
默认安装

一张表,里面有ID自增主键,当insert了17条记录之后,删除了第15,16,17条记录,再把Mysql重启,再insert一条记录,这条记录的ID是18还是15 ?

如果表的类型是MyISAM,那么是18。因为MyISAM表会把自增主键的最大ID 记录到数据文件中,重启MySQL自增主键的最大ID也不会丢失;

如果表的类型是InnoDB,那么是15。因为InnoDB 表只是把自增主键的最大ID记录到内存中,所以重启数据库或对表进行OPTION操作,都会导致最大ID丢失。

哪个存储引擎执行 select count(*) 更快,为什么?

MyISAM更快,因为MyISAM内部维护了一个计数器,可以直接调取。

  • 在 MyISAM 存储引擎中,把表的总行数存储在磁盘上,当执行 select count(*) from t 时,直接返回总数据。

  • 在 InnoDB 存储引擎中,跟 MyISAM 不一样,没有将总行数存储在磁盘上,当执行 select count(*) from t 时,会先把数据读出来,一行一行的累加,最后返回总数量。

InnoDB 中 count(*) 语句是在执行的时候,全表扫描统计总数量,所以当数据越来越大时,语句就越来越耗时了,为什么 InnoDB 引擎不像 MyISAM 引擎一样,将总行数存储到磁盘上?这跟 InnoDB 的事务特性有关,由于多版本并发控制(MVCC)的原因,InnoDB 表“应该返回多少行”也是不确定的。

数据类型

整数数据类型

日期数据类型

字符串数据类型

BLOB和TEXT有什么区别?

BLOB是一个二进制对象,可以容纳可变数量的数据。有四种类型的BLOB:TINYBLOB、BLOB、MEDIUMBLO和 LONGBLOB

TEXT是一个不区分大小写的BLOB。四种TEXT类型:TINYTEXT、TEXT、MEDIUMTEXT 和 LONGTEXT。

BLOB 保存二进制数据(如图片、声音),TEXT 保存字符数据。

索引

优势

  • 提高数据检索效率,降低数据库IO成本
  • 降低数据排序的成本,降低CPU的消耗

劣势

  • 索引也是一张表,保存了主键和索引字段,并指向实体表的记录,所以也需要占用内存
  • 虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段, 都会调整因为更新所带来的键值变化后的索引信息

索引分类

  • 从数据结构角度:B+树索引,Hash索引,Full-Text全文索引,R-Tree索引

  • 从逻辑角度:

    1
    2
    3
    4
    5
    
    > - 主键索引:主键索引是一种特殊的唯一索引,不允许有空值
    > - 普通索引或者单列索引:每个索引只包含单个列,一个表可以有多个单列索引
    > - 多列索引(复合索引、联合索引):复合索引指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用复合索引时遵循最左前缀原则,* 且联合索引的字段可以是任意顺序的 *
    > - 唯一索引或者非唯一索引
    > - 空间索引:空间索引是对空间数据类型的字段建立的索引,MYSQL中的空间数据类型有4种,分别是GEOMETRY、POINT、LINESTRING、POLYGON。MYSQL使用SPATIAL关键字进行扩展,使得能够用于创建正规索引类型的语法创建空间索引。创建空间索引的列,必须将其声明为NOT NULL,空间索引只能在存储引擎为MYISAM的表中创建
    

建索引案例

  • hash索引

    利用 CRC32() 创建 hash 字段索引,查询性能稳定,有额外的存储和计算消耗,不支持范围扫描。

    CRC全称为Cyclic Redundancy Check,又叫循环冗余校验。CRC32是CRC算法的一种,常用于校验网络上传输的文件。

    CRC32的基本特征:1.CRC32函数返回值的范围是0-4294967296(2的32次方减1),2.相比MD5,CRC32函数很容易碰撞

  • 前缀索引、后缀索引

    对一个长字符串类型的字段A,如果需要做查询功能,整个字段A加索引的话则很浪费存储空间,此时可以考虑建一个短字符串字段B并加上索引,

    前缀索引:截取字段A前几个字符

    后缀索引:截取字段A后几个字符

    选择前缀/后缀长度的原则是:区分度高 + 占用空间少,常用的字符长度为4,具体可以执行 sql 后再选择:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    -- 前缀索引截取长度判断
    SELECT
    count( DISTINCT LEFT ( FieldA, 3 ) ) AS L3,
    count( DISTINCT LEFT ( FieldA, 4 ) ) AS L4,
    count( DISTINCT LEFT ( FieldA, 5 ) ) AS L5,
    count( FieldA ) AS all
    FROM t;
    
    -- 后缀索引截取长度判断
    SELECT
    count( DISTINCT RIGHT ( FieldA, 3 ) ) AS L3,
    count( DISTINCT RIGHT ( FieldA, 4 ) ) AS L4,
    count( DISTINCT RIGHT ( FieldA, 5 ) ) AS L5,
    count( FieldA ) AS all
    FROM t;
    

哪些情况需要创建索引

  1. 主键自动建立唯一索引
  2. 频繁作为查询条件的字段
  3. 查询中与其他表关联的字段,外键关系建立索引
  4. 单键/组合索引的选择问题,高并发下倾向创建组合索引
  5. 查询中排序的字段,排序字段通过索引访问大幅提高排序速度
  6. 查询中统计或分组字段

哪些情况不要创建索引

  1. 表记录太少
  2. 经常增删改的表
  3. 数据重复且分布均匀的表字段,只应该为最经常查询和最经常排序的数据列建立索引(如果某个数据类包含太多的重复数据,建立索引没有太大意义)
  4. 频繁更新的字段不适合创建索引(会加重IO负担)
  5. where条件里用不到的字段不创建索引

MySQL 查询

count(*) 和 count(1)和count(列名)区别

执行效果上:

  1. count(*)包括了所有的列,相当于行数,在统计结果的时候,不会忽略列值为NULL
  2. count(1)包括了所有列,用1代表代码行,在统计结果的时候,不会忽略列值为NULL
  3. count(列名)只包括列名那一列,在统计结果的时候,会忽略列值为空(这里的空不是只空字符串或者0,而是表示null)的计数,即某个字段值为NULL时,不统计。

执行效率上:

  1. 列名为主键,count(列名)会比count(1)快,列名不为主键,count(1)会比count(列名)快『如果有主键,则 select count(主键)的执行效率是最优的』
  2. 如果表多个列并且没有主键,则 count(1) 的执行效率优于 count(*)
  3. 如果表只有一个字段,则 select count(*) 最优。

总结:count(主键) 优于 count(1) 优于 count(*)

mysql 的内连接、左连接、右连接有什么区别?什么是内连接、外连接、交叉连接、笛卡尔积呢?

join 图

性能分析

MySQL常见性能分析手段

在优化MySQL时,通常需要对数据库进行分析,常见的分析手段有慢查询日志,EXPLAIN 分析查询,profiling分析以及show命令查询系统状态及系统变量,通过定位分析性能的瓶颈,才能更好的优化数据库系统的性能。

性能下降SQL慢 执行时间长 等待时间长 原因分析

  1. 查询语句写的烂
  2. 索引失效(单值、复合)
  3. 关联查询太多join(设计缺陷或不得已的需求)
  4. 服务器调优及各个参数设置(缓冲、线程数等)

性能瓶颈定位

我们可以通过 show 命令查看 MySQL 状态及变量,找到系统的瓶颈:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Mysql> show status ——显示状态信息(扩展show status like ‘XXX’)

Mysql> show variables ——显示系统变量(扩展show variables like ‘XXX’)

Mysql> show innodb status ——显示InnoDB存储引擎的状态

Mysql> show processlist ——查看当前SQL执行,包括执行状态、是否锁表等

Shell> mysqladmin variables -u username -p password——显示系统变量

Shell> mysqladmin extended-status -u username -p password——显示状态信息

Explain(执行计划)

是什么:使用 Explain 关键字可以模拟优化器执行SQL查询语句,从而知道 MySQL 是如何处理你的 SQL 语句的。分析你的查询语句或是表结构的性能瓶颈

能干吗:

  • 表的读取顺序
  • 数据读取操作的操作类型
  • 哪些索引可以使用
  • 哪些索引被实际使用
  • 表之间的引用
  • 每张表有多少行被优化器查询

怎么玩:

  • Explain + SQL语句
  • 执行计划包含的信息(如果有分区表的话还会有partitions)

mysql

type 字段解释:

(显示查询使用了哪种查询类型,从最好到最差依次排列 system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL )

tip: 一般来说,得保证查询至少达到range级别,最好到达ref

  • system:表只有一行记录(等于系统表),是 const 类型的特例,平时不会出现
  • const:表示通过索引一次就找到了,const 用于比较 primary key 或 unique 索引,因为只要匹配一行数据,所以很快,如将主键置于 where 列表中,mysql 就能将该查询转换为一个常量
  • eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描
  • ref:非唯一性索引扫描,范围匹配某个单独值得所有行。本质上也是一种索引访问,他返回所有匹配某个单独值的行,然而,它可能也会找到多个符合条件的行,多以他应该属于查找和扫描的混合体
  • range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引,一般就是在你的where语句中出现了between、<、>、in等的查询,这种范围扫描索引比全表扫描要好,因为它只需开始于索引的某一点,而结束于另一点,不用扫描全部索引
  • index:Full Index Scan,index于ALL区别为index类型只遍历索引树。通常比ALL快,因为索引文件通常比数据文件小。(也就是说虽然all和index都是读全表,但index是从索引中读取的,而all是从硬盘中读的)
  • ALL:Full Table Scan,将遍历全表找到匹配的行

索引优化

  • 全值匹配我最爱
  • 最佳左前缀法则,比如建立了一个联合索引(a,b,c),那么其实我们可利用的索引就有(a), (a,b), (a,b,c)
  • 不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
  • 存储引擎不能使用索引中范围条件右边的列
  • 尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select i- s null ,is not null 也无法使用索引
  • like “xxxx%” 是可以用到索引的,like “%xxxx” 则不行(like “%xxx%” 同理)。like以通配符开头('%abc…')索引失效会变成全表扫描的操作,
  • 字符串不加单引号索引失效
  • 少用or,用它来连接时会索引失效
  • <,<=,=,>,>=,BETWEEN,IN 可用到索引,<>,not in ,!= 则不行,会导致全表扫描

一般性建议

  • 对于单键索引,尽量选择针对当前query过滤性更好的索引

  • 在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。

  • 在选择组合索引的时候,尽量选择可以能够包含当前query中的where字句中更多字段的索引

  • 尽可能通过分析统计信息和调整query的写法来达到选择合适索引的目的

  • 少用Hint强制索引

sql长度限制与占位符限制问题区分

  1. show variables like ‘max_allowed_packet’; sql 长度限制
  2. 1390: Prepared statement contains too many placeholders: MySQL预处理占位符数量限制65535。 插入m条数据n个?的占位符限制为 m*n<=65535, 或者IN查询占位符<=65535

mysql5.7.8 开始支持 json 字段查询

表存储结构

MySQL 5.7 的 JSON 类型

JSON 类型的优点

  • JSON 类型在 MySQL 内部以特殊的二进制方式存放,类似于 PostgreSQL 的 JSONB 类型。最大占用空间和 longtext 或者 longblob 一样,都是4G
  • SON数据类型,会自动校验数据是否为JSON格式,如果不是JSON格式数据,则会报错。
  • MySQL提供了一组操作JSON数据的内置函数。
  • 优化的存储格式,存储在JSON列中的JSON数据被转换成内部的存储格式。其允许快速读取。
  • 可以修改特定的键值,(如果之前在MySQL中存储过JSON格式字符串的小伙伴,应该经历过每次修改一个值,都要将整个JSON字符串更新一遍的尴尬。)

JSON 类型的缺点

  • JSON列与其他二进制类型列一样是无法创建索引。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 查看字段类型,是JSONObject类型还是JSONArray类型
select JSON_TYPE(val), JSON_TYPE(val_arr) from json_data

# JSONObject [like 查询],注意 `双引号`。『可以看到,对于 string 类型的 JSON 值,使用 -> 获取的都是被双引号包裹的字符串,MySQL 支持 column->>path 语法,通过 ->> 操作符获取纯量(scalar)』
select *, val->'$.input_3' from json_data where val->'$.input_3' like '"333自两地分%'
select *, val->>'$.input_3' from json_data where val->>'$.input_3' like '333自两地分%' # 不需要分号

# JSONObject [= 查询]
select *, val->'$.num' from json_data where val->'$.num'=3

# JSONArray [like 查询],注意 `["`
select val_arr->'$[*].input_0' from json_data where val_arr->'$[*].input_0' like '["000%'

Mysql 配置

  1. wait_timeout: 控制连接最大空闲时长

    如果你没有修改过MySQL的配置,缺省情况下,wait_timeout的初始值是28800。

    wait_timeout 过大有弊端,其体现就是MySQL里大量的SLEEP进程无法及时释放,拖累系统性能,不过也不能把这个指设置的过小,否则你可能会遭遇到“MySQL has gone away”之类的问题,通常来说,我觉得把wait_timeout设置为10是个不错的选择,但某些情况下可能也会出问题,比如说有一个CRON脚本, 其中两次SQL查询的间隔时间大于10秒的话,那么这个设置就有问题了。

    1
    2
    3
    
    mysql> set global wait_timeout=30;
    
    mysql> show global variables like 'wait_timeout';
    

窗口函数

MySQL从8.0开始支持窗口函数,这个功能在大多商业数据库中早已支持,也叫分析函数。

语法简介

语法:函数名([参数]) over(partition by [分组字段] order by [排序字段] asc/desc rows/range between 起始位置 and 结束位置)

函数解读:函数分为两个部分,

第一部分是函数名称,开窗函数的数量较少,只有11个窗口函数+聚合函数(所有聚合函数都可以用作开窗函数),根据函数性质,有的要写参数,有的不需要写参数;

第二部分是over语句,over()是必须要写的,里面有三个参数,都是非必须参数,根据需求选写:

1.第一个参数是 partition by + 分组字段,将数据根据此字段分成多份,如果不加partition by参数,那会把整个数据当做一个窗口。

2.第二个参数是 order by + 排序字段,每个窗口的数据要不要进行排序。

3.第三个参数 rows/range between 起始位置 and 结束位置,这个参数仅针对滑动窗口函数有用,是在当前窗口下分出更小的子窗口。其中起始位置和结束位置可写:current row 边界是当前行,unbounded preceding 边界是分区中的第一行,unbounded following 边界是分区中的最后一行,expr preceding 边界是当前行减去expr的值,expr following 边界是当前行加上expr的值。rows是基于行数,range是基于值的大小,到讲解到滑动窗口函数时再详细介绍。

1
2
3
4
5
// 示例
SELECT
    *, RANK() OVER(PARTITION BY cid ORDER BY score DESC) AS 'rank'
FROM
    scores;

窗口函数小结

类别 函数 说明
排序 ROW_NUMBER(连续排名) 为表中的每一行分配一个序号,可以指定分组(也可以不指定)及排序字段
/ DENSE_RANK(同分同名,不跳级) 根据排序字段为每个分组中的每一行分配一个序号。 排名值相同时,序号相同,序号中没有间隙(1,1,2,3这种)
/ RANK(同分同名,跳级) 根据排序字段为每个分组中的每一行分配一个序号。 排名值相同时,序号相同,但序号中存在间隙(1,1,3,4这种)
/ NTILE 根据排序字段为每个分组中根据指定字段的排序再分成对应的组
分布 PERCENT_RANK 计算各分组或结果集中行的百分数等级
/ CUME_DIST 计算某个值在一组有序的数据中累计的分布
前后 LEAD 返回分组中当前行之后的第N行的值。如果不存在对应行,则返回NULL。比如N=1时,第一名对应的值是第二名的,最后一名结果是NULL
/ LAG 返回分组中当前行之前的第N行的值。如果不存在对应行,则返回NULL。比如N=1时,第一名对应的值是是NUL,最后一名结果是倒数第2的值
首尾中 FIRST_VALUE 返回每个分组中第一名对应的字段(或表达式)的值,例如本文中可以是第一名的分数、学号等任意字段的值
/ LAST_VALUE 返回每个分组中最后一名对应的字段(或表达式)的值,例如本文中可以是最后一名的分数、学号等任意字段的值
/ NTH_VALUE 返回每个分组中排名第N的对应字段(或表达式)的值,但小于N的行对应的值是NULL

参考文章

MySQL8.0窗口函数之排名函数(rank、dense_rank)的使用

参考文章

MySQL 三万字精华总结 + 面试100 问,吊打面试官绰绰有余