最上层的客户端所包含的服务并不是 MySQL 独有的,大多数基于网络的客户端/服务器工具或服务器都有类似的服务,包括连接处理、身份验证、确保安全性等。
第二层包含了大多数 MySQL 的核心功能,包括查询解析、分析、优化、以及所有的内置函数(例如,日期、时间、数学和加密函数),所有跨存储引擎的功能也都在这一层实现:存储过程、触发器、视图等。
在旧版本中,MySQL 可以使用内部查询缓存(query cache)来查看是否可以直接提供结果。但是,随着并发性的增加,查询缓存成为一个让人诟病的瓶颈。从 MySQL 5.7.20 版本开始,查询缓存已经被官方标注为被弃用的特性,并在 8.0 版本中被完全移除。
第三层是存储引擎层。存储引擎负责 MySQL 中数据的存储和提取。存储引擎层还包含几十个底层函数,用于执行诸如“开始一个事务”或者“根据主键提取一行记录”等操作。但存储引擎不会去解析 SQL,不同存储引擎之间也不会相互通信,而只是简单地响应服务器的请求。
Innodb 默认支持行级锁,相比于 MyISAM 的表级锁而言更加细腻,对于并发事务的读写而言性能更高,行级锁的分类分为:间隙锁,临键锁,记录锁,
处理并发读/写访问的系统通常实现一个由两种锁类型组成的锁系统。这两种锁通常被称为共享锁(shared lock)和排他锁(exclusive lock),也叫读锁(read lock)和写锁(write lock)。其中资源上的读锁是共享的,或者说是相互不阻塞的。多个客户端可以同时读取同一个资源而互不干扰。写锁则是排他的,也就是说,一个写锁既会阻塞读锁也会阻塞其他的写锁
。
锁定策略是锁开销和数据安全性之间的平衡,这种平衡会影响性能
事务的实现:事务隔离性由锁来实现。原子性、致性、持久性通过数据库的 redolog 和 undo log 来完成。redo log 称为重做日志,用来保证事务的原子性和持久性。
隔离级别:
READ UNCOMMITTED(读未提交)在事务中可以查看其他事务中还没有提交的修改
读取未提交的数据,也称为脏读
READ COMMITTED(读已提交)B 事务只能看到 A 事务提交之后的数据和修改。这个级别会产生不可重复读问题
,因为 B 事务在 A 事务修改前后读取的数据可能会不一致,这意味着同一事务中两次执行相同语句,可能会看到不同的数据结果
REPEATABLE READ(可重复读)MYSQL 默认隔离级别,保证了在同一个事务中多次读取相同行数据的结果是一样的,解决了不可重复读问题。但不能解决幻读:当 A 事务在读取某个范围内的记录时,另外一个事务 B 又在该范围内插入了新的记录,当 A 事务再次读取该范围的记录时,会产生幻行。
SERIALIZABLE(可串行化)
幻读:可重复读隔离级别下,是存在幻读的问题:
Innodb 引擎为了解决「可重复读」隔离级别下的幻读问题,就引出了 next-key 锁,它是记录锁
和间隙锁
的组合。
Record Lock
,记录锁,锁的是记录本身;
Gap Lock
,间隙锁,锁的就是两个值之间的空隙,以防止其他事务在这个空隙间插入新的数据,从而避免幻读现象。
innodb MVCC 主要是为 Repeatable-Read 事务隔离级别做的。在此隔离级别下,A、B 客户端所示的数据相互隔离,互相更新不可见
MVCC 是行锁的变种,他在很多时候避免了加锁操作,大多时候可以实现非阻塞的读操作,写操作也只锁定特定的行因此细腻度更好,开销更低。
MVCC 的实现是通过保存数据的某个时间点的快照来实现的,因此不管执行多长时间,每个事务看到的数据都是一致的。根据事务开始的时间不同,每个事务对同一张表,且同一时刻看到的数据是不一样的。
InnoDB 中的 MVCC 设计: 在每条记录后面保存两个隐藏的列来实现,保存了行的创建时间和行的过期时间。(实际存储时候将系统版本号替代时间)每开始一个事务系统版本号会自动递增。
SELECT
确定行创建时间: InnnoDB 查找版本早于当前的事务版本的数据行,以确保当前事务读取的数据不晚于当前事务开始时间,
确定行删除时间:要么行删除版本未定义要么大于当前事务开始时间,确保事务读取的行在事务开始之前存在
确定了行创建时间和删除时间均符合要求才能够进行返回结果;
INSERT
新插入的每一行保存当前系统版本号作为行版本号
DELETE
删除的每一行保存当前的系统版本号作为行删除版本号
UPDATE
插入一行新的记录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为删除标识。
https://pdai.tech/md/db/sql-mysql/sql-mysql-mvcc.html
MySQL 的默认存储引擎是 InnodB,innodb 默认的索引结构为 B+树,B+树只有叶子节点才存储数据,非叶子节点存储键值,指向了索引的方向。叶子节点的结构为双向有序的数据链表
,因此他可以进行范围查找。同时 B+树一个节点可以存储多个孩子,使得 B+树显得更加矮胖,从而 B+树可以减少磁盘的查询次数并且不同于普通的二叉树,只需要遍历一次就可以得到所有的叶子数据,从而提高查找效率。
B+树:只有叶子节点才会存储数据,非叶子节点只存储键值,指向索引方向。叶子节点之间使用双向指针连接,最底层的叶子节点形成了一个双向有序链表,可以进行范围查找,同时为了维护底层叶子的有序性,在插入和删除时候需要对索引进行维护涉及到页面分裂和页面合并等操作。
为什么不用普通二叉树?
普通二叉树存在退化的情况,如果它退化成链表,相当于全表扫描。平衡二叉树相比于二叉查找树来说,查找效率更稳定,总体的查找速度也更快。
为什么不用平衡二叉树呢?
平衡二叉树每个节点只存储一个键值和数据的,B+树可以存储多个,同时树的高度也会更低那么对应的查找磁盘的次数也会少,
为什么用 B+ 树而不用 B 树呢
B+Tree 的磁盘读写能力相对于 B Tree 来说更强,IO 次数更少,B+Tree 永远是在叶子节点拿到数据,不需要遍历整个树就能拿到所有数据,所以 IO 次数是稳定的,叶子节点按照顺序排序,B+树的排序能力更强
索引,在 MySQL 中也叫作键(key)
优点:
● 索引大大减少了服务器需要扫描的数据量。
● 索引可以帮助服务器避免排序和临时表。
● 索引可以将随机 I/O 变为顺序 I/O。
索引类型分为主键索引
(聚簇索引
)和非主键索引
(二级索引
)。
非叶子节点包含了索引列和一个指向下级节点的指针
索引的主键不要使用 UUID 这样随机的主键值,它使得插入变得完全随机,且数据本身没有聚簇特性 。写入的数据不能按照顺序进行插入,需要耗费大量时间寻找合适的位置,且可能导致频繁的页分裂
使用 InnoDB 时应该尽可能地按主键顺序插入数据,并且尽可能地按照单调增加的聚簇键的值顺序插入新记录。
覆盖索引不等同于二级索引
如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为覆盖索引,即不需要进行回表操作。在一个覆盖索引中,如果索引是针对 (age, id, username) 创建的,那么叶子节点存储了按照 age 排序的索引列值,并且还包含了每个索引行对应的 id 和 username 列的值。当执行覆盖查询时,数据库引擎可以直接从这些叶子节点中获取所有需要的列值,从而避免了额外的数据页访问,提高了查询效率。
EXPLAIN 的 Extra 列可以看到“Using index”的信息,表示使用了覆盖索引
根据叶子的内容索引分为主键索引和非主键索引(只在叶子节点存放主键信息)
如果我执行
select * from T where k between 3 and 5
,需要执行几次树的搜索操作,会扫描多少行?其中 k 是非聚簇索引
可以看到,这个查询过程读了 k 索引树的 3 条记录,回表了两次。在这个例子中,由于查询结果所需要的数据只在主键索引上有,所以不得不回表。
.优化器怎么去看索引的区分度
一条语句可能命中多个索引,而优化器会选择合适的索引以最小的代价执行命令,我们使用 show index from table 可以查看索引的基数,即 Cardinality 字段的数字,基数越大反应了索引的区分度越大
select ID from T where k between 3 and 5
,这时只需要查 ID 的值,而 ID 的值已经在 k 索引树上了,因此可以直接提供查询结果,不需要回表。也就是说,在这个查询里面,索引 k 已经“覆盖了”我们的查询需求,我们称为覆盖索引
,避免了回表。覆盖索引
可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。
索引下推优化
能减少回表查询次数,提高查询效率。
索引下推的下推其实就是指将部分上层(Server 层)负责的事情,交给了下层(引擎层)去处理。使用索引下推当被索引的列为判断条件时,先一步 过滤出符合条件的数据没,减少了回表的次数,Innodb 索引的数据和索引在一起不存在回表,也就是说只有二级索引可以使用索引下推
查表- 选字段 - 对字段分组- 过滤-筛选-排序
having 只能用在 group by 之后
where 肯定在 group by 之前
注意若选取的列名和关键字相同 用""包起来表示别名
mysql 主要分为两种存储引擎,分别是 MylSAM 和 Innodb。两者的区别在于 MyIsm 只支持表锁且索引的为非聚簇索引结构为 B 树,而 innodb 引擎支持行锁,且支持聚簇索引,innodb 索引结构为 B+树
https://tobebetterjavaer.com/sidebar/sanfene/mysql.html#_26-怎么看执行计划-explain-如何理解其中各个字段的含义
执行计划中的 key 列显示了使用的索引名称
Extra 中的 using index 表明了使用了覆盖索引
行锁是细腻度最小的锁,相比于表锁和页面锁发生冲突的可能性最低。根据查询语句的限定行锁由记录锁、间隙锁和临建锁实现例如:
当使用唯一索引进行等值查找,例如 select * from user where id=9 时,innodb 会使用记录锁将 id=9 的数据进行加锁。而当使用 等值或者范围查询时例如 select * from t where id > 1 and id < 6 for update 会及将 id 在 1-6 范围内的数据使用间隙锁进行加锁。select * from t where id > 1 and id <= 6 for update 当查询数据区间在 1-6 且包含边界时,即命中了部分 记录例如(13456) 时会利用记录锁加锁同时将 1-5 之间的数据使用间隙锁进行加锁,防止其他事务在间隙中插入新行,这种结合记录锁和间隙锁的方式成为临键锁
而当使用唯一索引并匹配到了记录时,临建锁会退化为记录锁,
外键
查看慢查询 使用 MySQL 的慢查询日志定位问题语句
优化:
从三个方面进行回答
1、数据库设计;
2、索引设计;
3、查询优化;
show index from table
select 语句查看执行计划 explain 根据执行计划中的列 type 显示关联类型或访问类型
如果是 ALL 则是全表扫描,Key 列中会显示实际使用的索引。查看 Extra 列 == Using index:表示 MySQL 将使用覆盖索引,避免了回表
在一张表中建立多个单列索引 A、B,查询条件 A=1and B=2 时索引的优化器会选择如何使用索引最高效,因此虽然两个索引都可以用但是若只选择一个索引查询效率更加高效也只选择一个索引,若是在 mysql5.7 版本后并且 INnnoDB 索引中使用 OR 会将两个索引都用上
多个单列索引在多条件查询时优化器会选择最优索引策略,可能只用一个索引,也可能将多个索引全用上! 但多个单列索引底层会建立多个 B+索引树,比较占用空间,也会浪费一定搜索效率,故如果只有多条件联合查询时最好建联合索引!
建立索引 create index tb on table(a,b,c)
联合索引失效的场景 查询 a=1 c=2 b=2 这样的查询顺序会失效吗
不会失效,查询优化器会自动优化
原因:
alter table
testadd index search(a,b,c,d)
建立联合索引 a b c d
联合索引遵循最左匹配原则,mysql 会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,当 a>2 时即使 a 为最左匹配同样不能使用索引,当查询 b=2 c=2 时候未使用最左匹配 不能使用索引。其中使用=和 in 时 abcd 的索引无论为顺序还是乱序,查询优化器会自动优化可识别的形态
联合索引必须按照顺序使用,类似电话簿中的查询,若不知道姓,那查询起来将毫无用处,
当建立一个联合索引 (a,b,c,d) 时,查询条件 a, b, c, d 的顺序不会影响是否使用索引。
https://blog.csdn.net/qq_35275233/article/details/87888809
https://blog.csdn.net/Abysscarry/article/details/80792876
binlog 二进制日志 记录数据库所有的更改操作,主要用于数据的备份和恢复
redo log 重做日志 保存了事务的持久性 用于数据宕机后的数据恢复
undo log 回滚日志 保证了事务的原子性,用于事务的回滚和 MVCC
redo log undo log 是 mysql 中 innodb 引擎独有的
当数据库宕机后,mysql 根据 redolog 进行恢复数据库,保持数据的持久性。记录了数据提交后的数据状态
undo log 回滚日志 记录了在事务提交之前的数据状态
begin
update user set age= 1 where id=1;
begin
update user set age =1 where id =2;
此时两个事务均为提交获取到各自的排他锁
事务 1 执行
update user set age = 3 where id = 2;
事务 1 会一直等待释放
update user set age = 2 where id =1;
2 等待 1 释放 进入死锁
利用执行计划 explain 查看查询语句的执行计划 查看其中的 type 字段是否使用了索引
查看表是否有索引 使用 show index from table 的命令
聚合函数在 SQL 中用于对数据进行统计和分组计算,而 HAVING 子句通常与 GROUP BY 子句一起使用。HAVING 子句允许对分组后的结果进行筛选,仅返回满足特定条件的分组。
查表- 选字段 - 对字段分组- 过滤-筛选-排序
having 只能用在 group by 之后
where 肯定在 group by 之前
注意若选取的列名和关键字相同 用""包起来表示别名
SELECT column1, column2
FROM table
WHERE condition
GROUP BY column
HAVING condition
ORDER BY column
LIMIT number;
计算时间差
TIMESTAMPDIFF(unit, start_date, end_date) 计算时间差
unit:HOUR(小时)
DAY(天)
WEEK(周)
MONTH(月)
USING 是用于指定连接条件的关键字,通常用于 JOIN 操作中,连接条件是基于列名的相等匹配
JOIN table2 USING (column_name);
使用 ON 语法时,连接条件是基于一个或多个列之间的逻辑表达式,可以使用比较运算符(如等于、大于等)
JOIN table2 ON table1.column_name = table2.column_name;
year(列名)=时间 提取年份为指定时间的列
group by 子句后面的列名指定了用于分组的列。所有具有相同值的行会被分为同一组
avg(col)计算指定列的平均值
IF(condition, value_if_true, value_if_false)
ROUND(number, decimals)四舍五入
连接字符
CONCAT(string1, string2, ...)其中,string1、string2 等是要连接的字符串参数。你可以根据需要提供多个参数。
limit
TIMESTAMPDIFF(unit, start_date, end_date) 计算时间差
unit:HOUR(小时)
DAY(天)
WEEK(周)
MONTH(月)
0
count 去重 注意不需要,分割
TIMESTAMPDIFF(unit, start_date, end_date) 计算时间差
unit:HOUR(小时)
DAY(天)
WEEK(周)
MONTH(月)
1
case where 表达式
CASE WHEN 表达式可以灵活地在 SELECT 查询、WHERE 条件和 ORDER BY 子句等部分中使用,以满足特定需求。
TIMESTAMPDIFF(unit, start_date, end_date) 计算时间差
unit:HOUR(小时)
DAY(天)
WEEK(周)
MONTH(月)
2
#{}
:Mybatis 在处理#{}时,是预编译处理,将参数以带引号的占位符的方式填充,能够防止 sql 注入。
${}:是字符串替换,会将 SQL 中的${}替换成变量的实际值。
使用#{}可以有效的防止 SQL 注入,提高系统的安全性。无论内容是什么,都以普通字符串参数去解析
SQL 注入 在字符串中注入 SQL 语句,并在执行过程中忽略了字符检查使得数据库执行了恶意的语句并得到意料之外的结果
主键作为一条记录的唯一标识,不能有重复 且不为空
如果数据 A 持有某事务的排它锁(Exclusive Lock),那么其他事务对数据 A 加共享锁(Shared Lock)或排它锁都会失败
SQL 的执行顺序:
第一步:执行 FROM 确定具体的数据源
第二步:WHERE 条件过滤 对数据源源进行过滤排除无关数据
第三步:GROUP BY 分组 对目标数据进行分组
第四步:执行 SELECT 投影列 对分组之后的数据进行选择
第五步:HAVING 条件过滤 对选择后的数据进行过滤
第六步:执行 ORDER BY 排序 对整体的结果进行排序
GROUP BY 是 SQL 中用于分组数据的子句,其核心作用是将数据按指定字段的值进行分组,每一组数据会作为一个整体进行后续的操作(例如聚合计算)。
GROUP BY
的主要操作是:
按字段分组:把表中具有相同字段值的行归为一组。
应用聚合函数:对 group by 分组后的每组数据中的对应字段进行计算,例如求和 (SUM
)、求平均值 (AVG
)、计数 (COUNT
)、最大值 (MAX
)、最小值 (MIN
) 等。
分组后,每组数据会返回一条结果。简单理解就是 order by 就是将相同的指定值的记录分为一个组,比如将员工按照部门进行分组;
分组之后每组对应的字段会返回一个数据,而对于未包含在 group by 中的字段,而若使用了确定的聚合函数就可以指定返回的数据,若没有明确的聚合逻辑,那么 mysql 会随机选择一个对应选择的字段值作为返回;
TIMESTAMPDIFF(unit, start_date, end_date) 计算时间差
unit:HOUR(小时)
DAY(天)
WEEK(周)
MONTH(月)
3
我们按照 creat_by 进行分组但是 id 没有使用具体的聚合逻辑,实际返回的 id 数据便是 mysql 随机选择的 id 值。
按照 2 表示的查询便是指定了具体的聚合逻辑 max 返回了每组中的最大值
严格来说 在分组查询中,如果一个字段既不在 GROUP BY 子句中,也没有被聚合函数包裹,那么数据库无法确定这个字段在每个分组中应该取哪一行的数据,因为分组后一个组可能包含多行。而 MySQL 遵循非严格意义上的分组语法 ,作为 mysql 自己的一种特性存在。
having 的作用是对分组之后的数据进行过滤,当没有使用分组的情况下 having 的意义等同于 where。
TIMESTAMPDIFF(unit, start_date, end_date) 计算时间差
unit:HOUR(小时)
DAY(天)
WEEK(周)
MONTH(月)
4
过滤顺序晚于 GROUP BY
HAVING
的过滤是在分组完成后进行的,作用对象是分组后的结果集。WHERE
则是在分组之前进行过滤,作用对象是原始数据。必须引用分组或聚合的字段
HAVING
时,过滤条件通常需要引用分组字段或聚合字段,否则可能会导致语法错误。order by 一般对最后的结果进行整理排序,分为升序和降序 默认是升序 ASC 降序 DESC
排序过程根据要求的字段顺序进行,ORDER BY
子句中,排序规则按照给定的多个列进行依次排序。如果前面的列无法区分出记录的顺序,就会依次使用后面的列进行排序。
返回指定结果的前几行数据
偏移量语法:LIMIT row_count OFFSET offset
; 或者 LIMIT row_count , offset
;
offset
:从第几条记录开始,索引从 0 开始。row_count
:返回的最大记录数。即从 offset
记录开始返回row_count
条记录
LIMIT
查询随着OFFSET
增大,性能会下降,因为数据库仍然需要扫描和丢弃前面的记录
出现深分页的问题 MP 使用的分页查询便是 limit 关键字实现的
TIMESTAMPDIFF(unit, start_date, end_date) 计算时间差
unit:HOUR(小时)
DAY(天)
WEEK(周)
MONTH(月)
5
偏移量语法:LIMIT row_count OFFSET offset; 或者 LIMIT row_count , offset;
offset
:从第几条记录开始,索引从 0 开始。row_count
:返回的最大记录数。即从 offset 记录开始返回row_count
条记录
LIMIT
查询随着OFFSET
增大,性能会下降,因为数据库仍然需要扫描和丢弃前面的记录
出现深分页的问题 MP 使用的分页查询便是 limit 关键字实现的
嵌套在其他查询中的查询,将一个查询的结果作为另一个的条件或者数据。子查询可以返回单个数值,可以用作比较或赋值,或者返回单列数据集,
用于 in 操作