一、MySQL体系结构
● 连接层:
最上层是一些客户端和连接服务,主要完成一些类似于连接处理、授权认证、及相关的安全方案。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
● 服务层:
第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化,部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程、函数等。
● 引擎层:
存储引擎真正的负责了MySQL数据的存储和提取,服务器通过API和存储引擎进行通信。不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。
● 存储层:
主要是将数据存储在文件系统之上,并完成与存储引擎的交互。
二、存储引擎
1、存储引擎简介:
存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式。存储引擎是基于表的。而不是基于库的,所有存储引擎也可被称为表类型。
2、在创建表时指定存储引擎:
CREATE TABLE 表名(
字段1 字段1类型 [COMMENT 注释],
...
字段n 字段n类型 [COMMENT 注释]
) ENGINE = INNODB [COMMENT 表注释];
3、查看当前数据库支持的存储引擎:
SHOW ENGINES;
4、存储引擎特点:
● InnoDB:
特点:支持事务、支持外键、支持行级锁。
● MyISAM:
特点:不支持事务,不支持外键,支持表锁,不支持行锁,访问速度快
● Memory:
特点:数据存储在内存中,访问速度快,支持hash索引
5、存储引擎选择:
● InnoDB:是MySQL的默认存储引擎,支持事务、外键。如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询之外,还包含很多的更新、删除操作,那么InnoDB存储引擎是比较合适的选择。
● MyISAM:如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性和并发性要求不是很高,那么MyISAM是比较合适的。
● Memory:将数据保存在内存中,访问速度快,通常用于临时表和缓存。但是Memory对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性。
三、索引
请参阅另一篇博客数据库优化之索引
四、SQL性能分析
● 查看SQL执行频率
SHOW GLOBAL STATUS LIKE 'Com_______';--模糊匹配七个下划线
此时会显示SQL语句的执行频次
● 慢查询日志
慢查询日志是记录了所有执行时间超过指定参数的(long_query_time,单位:秒,默认10秒)的所有SQL语句的日志。
查看是否开启慢查询日志:
SHOW variables like 'slow_query_log';
MySQL的慢查询日志默认没有开启,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息(配置完成之后需重启MySQL服务):
# 开启MySQL慢查询日志开关
slow_query_log = 1
# 设置慢日志的时间为2秒,SQL语句执行时间超过两秒,就会视为慢查询,记录慢查询日志
long_query_time = 2
● profile详情
SHOW profiles 能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。通过 have_profiling 参数,能够看到当前MySQL是否支持profile操作:
SELECT @@have_profiling;--查看是否支持profile操作
默认profile是关闭的,可以通过set语句在session/global级别开启profiling
SELECT @@profiling;--查看profile是否打开
SET profiling = 1;--开启profiling
执行一系列的业务SQL操作,然后通过如下指令查看指令的执行耗时:
SHOW profiles;--查看每一条SQL的耗时基本情况
SHOW profile for query query_id;--查看指定query_id的SQL语句各阶段的耗时情况
SHOW profile CPU for query query_id;--查看指定query_id的SQL语句CPU的使用情况
● explain执行计划
EXPLAIN 或者 DESC 命令获取MySQL如何执行SELECT语句的信息,包括在SELECT语句执行过程中表如何连接和连接的顺序。
EXPLAIN SELECT 字段列表 FROM 表名 WHERE 条件
EXPLAIN执行计划各字段含义:
id:
select查询的序列号,表示查询中执行select子句或者是操作表的顺序(id相同,执行顺序从上到下;id不同,值越大,越先执行)。
select_type:
表示select的类型,常见的取值有 :
SIMPLE(简单表,即不使用表连接或子查询)、
PRIMARY(主查询,即外层的查询)、
UNION(UNION中的第二个或者后边的查询语句)、SUBQUERY(SELECT/WHERE之后包含的子查询)等
type:
表示连接类型,性能由好到差的连接类型为:
NULL(不查询任何表)、system(查询系统表)、const、eq_ref(使用唯一索引)、ref(使用非唯一索引)、range、index、all
possible_key:
显示可能应用在这张表上的索引,一个或多个
key:
实际使用的索引,如果为NULL则没有使用索引。
key_len:
表示索引中使用的字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好。
rows:
MySQL认为必须要执行查询的行数,在InnoDB引擎的表中,是一个估计值,可能并不总是准确的。
filtered:
表示返回结果的行数占需读取行数的百分比,filtered的值越大越好。
Extra:
表示查询的额外信息
五、SQL优化
1、数据库三大范式:
● 属性不可分割
● 数据表里的每一条数据,都是可唯一标识的,而且所有的非主键字段,都必须完全依赖主键,不能只依赖主键的一部分。
● 数据表中的每一个非主键字段都和主键字段直接相关
2、sql语句在MySql中的执行过程:
(1)连接器:进行身份认证和权限认证。
(2)查询缓存:执行查询语句的时候会先查询缓存。(MySQL 8.0 版本后移除)
(3)分析器: 没有命中缓存的话,SQL 语句就会经过分析器,先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。
(4)优化器:按照 MySQL 认为最优的方案去执行。
(5)执行器:执行语句,然后从存储引擎返回数据。
(6)日志模块(binlog):归档日志,MySQL 自带的日志模块式,所有引擎都可用。
注:查询缓存被移除的原因:
锁粒度问题:查询缓存的实现可能导致在高并发情况下频繁的锁竞争,影响整体数据库性能。
内存管理开销:维护查询缓存需要消耗大量内存资源
并发性能问题:查询缓存对于高并发环境下的性能并没有显著提升,反而可能引起性能下降。
复杂的失效策略:缓存的命中率不高,且在数据变更时需要进行缓存的失效和刷新,导致复杂的策略和额外的性能开销。
3、sql中关键字的执行顺序:
from、on、join、where、group by、having、select、distinct、union、order by、limit
4、sql优化方案:
(1)避免使用select *
(2)使用join时,小表在前大表在后,并且不要连接过多的表,一般不超过五个。因为MySql中存在关联缓存,多关联一个表就会多一个关联缓存,就会占用更多的内存。
(3)尽量避免使用子查询,通常情况下可以将子查询在 in 子句中,并且子查询中为简单 SQL(不包含 union、group by、order by、limit 从句)的Sql语句转化为关联查询。因为子查询的结果集通常会被存储到临时表中,不论是内存临时表还是磁盘临时表都不会存在索引,因此会消耗过多的 CPU 和 IO 资源,产生大量的慢查询。
(4)子查询的时候子查询表大的用exists,子查询表小的用in。因为in是把外表和内表做hash连接,exists是对外表做loop循环,所以in用到 的是外表的索引,exists用到的是内表的索引。
(5)尽量用 union all 代替 union,union 会去重,union all 不会。
(6)尽量使用varchar代替char,因为varchar是变长,char是定长,变长更节省空间
(7)禁止使用不含字段列表的 insert 语句
(8)拆分复杂的大 SQL 为多个小 SQL
insert优化:
需要一次性插入多条数据时可以通过批量插入、手动提交事务、按主键顺序插入的方式进行优化。大批量插入数据时(几百万)可以使用load指令
主键优化:
(1)降低主键的长度
(2)尽量不要使用UUID或其他的自然主键比如身份证号(无序、过长)
(3)业务操作时,避免修改主键
页分裂:主键乱序插入
页合并:当页中删除的记录达到一定的阈值(默认为页的50%),InnoDB会寻找最靠近的页,看看是否可以将两个页合并以优化空间
索引优化:
(1)索引不要建立在重复值较多的字段上,比如性别
(2)合理使用覆盖索引,减少回表
(3)合理控制索引的数量,一般不超过六个,因为索引在提高查询效率的同时会降低insert和update的效率(可能会重建索引)
(4)避免索引失效,导致索引失效的七种情况见https://editor.csdn.net/md/?articleId=109487925:
(5)更新数据时避免行锁升级为表锁
5、一条sql执行的很慢的原因:
大多数情况正常偶尔很慢的情况
(1)数据库可能正在刷新脏页,就是将内存中的数据同步到磁盘上
(2)执行时遇到锁,可以使用 show processlist命令来查看当前状态
一直很慢的情况
(1)字段没有设置索引或设置了索引没有走(通过索引区分度来预测通过哪种方式进行查询I/O操作较少)
六、MVCC机制(多版本并发控制)
快照读:读取的是历史数据,不会读取最新的数据(select)
当前读:读取的是最新的数据,不会读取历史数据(select…for update、select…lock in share mode、update、delete、insert)
支持数据并发修改场景下的快照读
是为了解决脏读、不可重复读等事务之间的读写问题而产生的,主要是基于undo log(版本连)和read view实现的。
当执行update或delete操作时,会将每次操作记录在undolog中,当执行查询操作时会生成Read View,而Read View里面包含四个属性,分别是:当前活跃事务的编号集合、最小活跃事务编号、预分配事务编号(就是当前最大事务编号+1)和Read View创建者的事务编号。查询时会从最新版本的那条数据开始通过对事务编号的比较来判断当前数据对当前事务是否可见。比较的规则就是
1、判断当前数据的事务编号是否等于创建该read view的事务编号,如果等于就说明该数据是由创建当前read view的事务修改的,那该条数据就是对当前事务可见的。如果不等于则继续判断
2、当前数据的事务编号是否小于最小事务编号,如果小于说明该条数据已经被提交过,他对当前事务是可见的,反之则继续判断
3、当前数据的事务编号是否大于预分配事务编号,如果大于说明该数据是由预分配事务提交的,对当前事务是不可见的。
4、最后判断当前数据的事务编号是否大于等于最小事务编号,小于等于预分配事务编号。如果在这个区间内再判断当前数据的事务编号是否在当前活跃事务编号集合中。如果在说明该数据还未提交,是不可见的。反之就是可见的。
一条数据在经过以上规则判断之后如果全不满足,就会从版本链中获取下一条数据继续判断,直到找出可见数据或判断到最后一条数据为止。
需要注意的是在RC的事务隔离级别下每次执行快照读都会生成一个新的read view,会出现不可重复读的问题。
在RR的事务隔离级别下仅在第一次执行快照读的时候生成Read View,后续快照读复用。如果在同一事务中的两次快照读之间产生了当前读(insert、update、delete、for update),就会产生幻读的问题。
七、MySQL中的日志、缓冲池
1、undo log(回滚日志):是 Innodb 存储引擎层生成的日志,实现了事务中的原子性,主要用于事务回滚和 MVCC。
2、redo log(重做日志):是 Innodb 存储引擎层生成的日志,实现了事务中的持久性,主要用于掉电等故障恢复;
3、binlog (归档日志):是 Server 层生成的日志,主要用于数据备份和主从复制;
4、relaylog (中继日志):用于主从复制时接收binlog日志;
5、slowlog (慢日志):用于定位慢SQL
6、errorlog (错误日志):mysql运行遇到问题时记录的日志
binlog日志的三种格式:
STATEMENT:记录的是SQL语句
ROW:记录的是每一行的数据变更
MIXED:默认日志格式,混合了STATEMENT和ROW两个格式
Buffer Pool(缓冲池):
因为MySQL中的数据都是存在磁盘中的,如果我们要修改一条数据,就要先将这条数据从磁盘中取出,在内存中做修改,修改完之后,并不会马上刷回磁盘,而是放在Buffer Pool中,如果后续有查询命中了这条数据,可以从缓冲池中直接返回。大大提高了数据库的读写性能。如果要修改的数据在缓冲池中存在,那他会直接修改缓冲池中对应的行数据,然后将数据所在的页设为脏页,为了减少磁盘I/O,不会立即将脏页写入磁盘,而是后续由后台线程选择一个合适的时机将脏页写入磁盘
Buffer Pool 缓存什么:
Buffer Pool 除了缓存「索引页」和「数据页」,还包括了 Undo 页,插入缓存、自适应哈希索引、锁信息等等。
八、MySQL两阶段提交原理
MySQL的两阶段提交(2PC)原理主要涉及两个阶段:准备阶段(Prepare)和提交阶段(Commit)
1、在准备阶段,InnoDB会将事务的redolog写入磁盘,但不会立即提交。事务的状态被设置为TRX_UNDO_PREPARED
。同时将事务更新产生的redolog刷入磁盘
2、在提交阶段,InnoDB会将事务的binlog写入文件并刷入磁盘。事务的状态被设置为COMMIT
或ROLLBACK
,具体取决于事务对应的binlog是否完整。如果binlog是完整的,则提交事务;否则,回滚事务。在redolog中,事务的状态被设置为COMMIT
,表示事务已经提交。同时,记录事务对应的binlog偏移,并写入系统表空间,以便在恢复时使用。
两阶段提交的主要目的是保证redolog和binlog的数据一致性。如果redolog已经提交,则直接提交事务;如果redolog处于准备阶段,则检查事务对应的binlog是否完整。如果binlog是完整的,则提交事务;否则,回滚事务。这样可以确保在发生系统崩溃时,主库和从库的数据是一致的,从而避免主从不一致的问题。
九、MySQL主从复制
主从复制的过程:
MySQL 的主从复制依赖于 binlog,复制的过程就是将 binlog 中的数据从主库传输到从库上的过程。
1、首先主库会将操作语句记录到 binlog 中。并且生成一个log dump线程,负责将 binlog 日志传输给从库的I/O线程。
2、然后从库会创建一个专门的 I/O 线程,来请求主库的 binlog 日志,并将这些日志写入到从库的 relaylog 中继日志中。
3、最后从库会再创建一个SQL线程,去读取 relaylog 中继日志,并执行这些操作,最终实现主从的数据一致性。
在实际使用中,一个主库一般跟 2~3 个从库(1 套数据库,1 主 2 从 1 备主)即可。