基础
书写顺序
执行除了 select
是第一个, 其余顺序是一样的
执行顺序: select
再 Having
后执行 (执行顺序也为联合索引声明循序) ^ib46t0
执行顺序则是由 MySQL 查询优化器根据查询计划决定的,这可能包括选择最佳的索引、优化连接顺序、以及其他一些优化策略
SELECT
阶段 (可以聚合函数)(可以别名)FROM
阶段 (JOIN ON
)(执行先ON
后JOIN
)(可以起别名)(执行顺序第一)WHERE
阶段 (效率高)(不能聚合函数)(不能起别名,可以用之前起的别名)(能写在 where 不写在 having)GROUP BY
阶段HAVING
阶段 (效率低) 可以使用分组数据 (可以聚合函数)(无需和 where 成对出现)ORDER BY
阶段ASC升序/DESC降序
排序字段未必是select
中的 (可以起别名) (默认升序) (消耗资源,减少使用)LIMIT X,Y;
X 位移偏移量, Y 条目数, 查询第 X 条数据开始后的 Y 条数据
LIKE
: 模糊查询,可以在 JOIN ON
/ WHERE
/ HAVING
%
: 代表不确定个数的字符 (0 个,1 个,或多个)
mysql
自带的核心数据库,用户账户和权限信息information_schema
维护的所有其他数据库的信息 (如表的索引约束)performance_schema
监控 Mysql 服务的各类性能指标 (如锁的信息)sys
自带的数据库,通过视图的形式把performance_schema
和information_schema
结合起来
DESC [表/字段]
导入现有的表 (`mysql` 命令行) : source d:\mysqldb.sql
- 进入/切换数据库
USE database_name;
- SHOW : 通常用于显示多个变量或状态,可以一次性查看所有相关信息。
- - 通常不区分全局和会话,直接显示当前上下文中的变量。
SHOW DATABASES:显示所有数据库的列表
SHOW TABLES:显示当前数据库中的所有表
SHOW TABLE STATUS LIKE '表':查看对应表状况
SHOW COLUMNS FROM table_name:显示指定表的列信息
SHOW CREATE TABLE table_name:显示创建指定表的 SQL 语句
SHOW STATUS:显示服务器状态信息。
SHOW PROFILES:显示最近执行的查询的性能分析信息。可以用于对比多个查询的性能,适合深入分析。
SHOW ENGINES:显示支持的存储引擎及其状态。
SHOW VARIABLES:显示系统变量及其当前值。
查看页大小
show variables like '%innodb_page_size%'
SHOW INDEX FROM table_name:显示该表的索引
- @@ 前缀 : 可以针对特定变量进行查询,方便获取单一变量的值。
- - 可以用来查询全局变量和会话变量,支持使用 `global` 或 `session` 前缀。
@@ 前缀变量是 MySQL 服务器的内部状态变量,它们不由任何具体的数据库表存储,而是由服务器进程在内存中直接管理。 它们代表了 MySQL 服务器的配置、状态和运行时参数,通过 SELECT @@variable_name 可以直接访问这些值。 这些值是服务器实时状态的反应,而不是静态存储在数据库中的数据。
SELECT @@global.variable_name;:查询全局变量。
SELECT @@session.variable_name;:查询当前会话的变量。
SELECT @@max_connections;查询最大连接数。
SELECT @@connections; 查询总连接数
SELECT @@version;:获取当前MySQL版本。
SELECT @@datadir;:获取数据目录路径
SELECT @@innodb_buffer_pool_size; 查询InnoDB缓冲池大小
SELECT @@thread_count; 查询当前线程数
SELECT @@query_cache_size; 查询查询缓存的大小
select @@optimizer_switch
SELECT DATABASE();
SELECT USER();
SELECT UUID(); 返回一个唯一标识符
SELECT * FROM INFORMATION_SCHEMA.table_constraints WHERE TABLE_NAME = '表名称'; 表约束情况
SELECT * FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_NAME = '表名称'; 表索引情况
SHOW TABLE STATUS LIKE 'countries';表约束情况,没有那么详细
不用时要关闭,消耗性能
- 查看默认行格式
select @@innodb_default_row_format;
或
show table status like'表';
- 指定行格式
Create Table 表名 ROW_FORMAT=行格式
Alter Table 表名 ROW_FORMAT=行格式
SHOW PROFILES:显示最近执行的查询的性能分析信息。包括查询的执行时间、锁定时间等。它提供了更详细的性能数据,帮助开发者识别性能瓶颈
> 某个环节时间长则可考虑优化
show profile for query 7;
show profile cpu,block io for query 6;
SHOW STATUS:显示服务器状态信息
> 上次查找开销,反映了查询的复杂度和资源消耗
SHOW STATUS LIKE 'last_query_cost';
我们可以通过
select * from performance_.schema.data_locks\G;
这条语句,查看事务执行SQL过程中加了什么锁。
- 如果 LOCK_MODE 为 `X`,说明是 next-key 锁;
- 如果 LOCK_MODE 为 `X, REC_NOT_GAP`,说明是记录锁;
- 如果 LOCK_MODE 为 `X, GAP`,说明是间隙锁;
title:DDL增删改
[[Mysql/Mysql优化#索引的创建使用]]
```mysql
创建表
CREATE TABLE [IF NOT EXISTS] 表名(
字段1, 数据类型 [约束条件] [默认值],
字段2, 数据类型 [约束条件] [默认值],
字段3, 数据类型 [约束条件] [默认值],
[表约束条件]
[constraint 自定约束名] unique key(字段名)
[CONSTRAINT <外键约束名称>] FOREIGN KEY(从表的某个字段) references 主表名(被参考字段)
);
删除表
DROP TABLE [IF EXISTS] 数据表1 [, 数据表2, …, 数据表n];
重命名表
RENAME TABLE 表名 TO 新表名;
复制表结构
CREATE TABLE 新表名 LIKE 旧表名;
复制表数据
INSERT INTO 新表名 SELECT * FROM 旧表名;
清空表
TRUNCATE TABLE 表名;
修改表中字段
增 ALTER TABLE 表名 ADD 字段名 字段类型 [FIRST|AFTER 字段名] [约束名]
改(很少用,对默认值的修改只影响今后对表的修改)
ALTER TABLE 表名 MODIFY 字段名 字段类型 [DEFAULT 默认值] [FIRST|AFTER 字段名]
[约束条件];
ALTER TABLE 表名 CHANGE 列名 新列名 新数据类型;
删 ALTER TABLE 表名 DROP 字段
删除约束
ALTER TABLE 表名 DROP PRIMARY KEY;
删除唯一约束 ALTER TABLE 表名 DROP INDEX 约束名;
删除外键 ALTER TABLE 表名 DROP FOREIGN KEY 约束名;
索引
在声明有主键约束、唯一性约束、外键约束的字段上,会自动的添加相关的索引
创建索引
CREATE TABLE table_name [col_name data_type]
[UNIQUE | FULLTEXT | SPATIAL] [INDEX | KEY] [index_name] (col_name [length]) [ASC | DESC]

增加索引
()必须带
ALTER TABLE table_name ADD [UNIQUE | FULLTEXT | SPATIAL] [INDEX | KEY]
[index_name] (col_name[length],...) [ASC | DESC]
在 MySQL 中,`CREATE INDEX` 被映射到一个 `ALTER TABLE` 语句上
CREATE INDEX 索引名 ON 表名 (字段名);
CREATE [UNIQUE | FULLTEXT | SPATIAL] INDEX index_name
ON table_name (col_name[length],...) [ASC | DESC]
删除
DROP INDEX 索引名 ON 表名;
ALTER TABLE table_name DROP INDEX index_name;
`AUTO_INCREMENT` 作用的字段要么有唯一约束,要么有主键约束,因此不能删除
如果删除作为索引的字段,该索引也会被删除

Mysql8.0支持创建降序索引
切换索引可见状态
ALTER TABLE tablename ALTER INDEX index_name INVISIBLE; #切换成隐藏索引
ALTER TABLE tablename ALTER INDEX index_name VISIBLE; #切换成非隐藏索引
title:DML增删改
```mysql
- 增(使用单条 `INSERT` 语句插入多行记录在性能上通常优于使用多条单行 `INSERT` 语句)
INSERT INTO table_name(column1 [, column2, …, columnn])
VALUES
(value1 [,value2, …, valuen]),
(value1 [,value2, …, valuen]),
……
(value1 [,value2, …, valuen]);
将数据从一个表插入到另一个表。
INSERT INTO 目标表名
(tar_column1 [, tar_column2, …, tar_columnn])
SELECT
(src_column1 [, src_column2, …, src_columnn])
FROM 源表名
[WHERE condition]
改,必须使用where
UPDATE table_name
SET column1=value1, column2=value2, … , column=valuen
[WHERE condition]
#使用 WHERE 子句指定需要更新的数据,如果省略 WHERE 子句,则表中的所有数据都将被更新。
删除(必须where
DELETE FROM table_name [WHERE <condition>];
START TRANSACTION;
COMMIT;
ROLLBACK;
逻辑架构
连接层
: TCP 连接 Mysql, 做身份认证,权限获取服务层
:SQL interface
SQL 执行先进行词法分析
(关键字、标识符、常量、运算符等), 然后语法分析
: 根据词法分析结果判断输入的 SQL 语句是否满足 Mysql 语法Parser
解析器, 将词法分析生成的词法单元,按照 SQL 语法规则进行解析,构建出一颗抽象语法树Optimizer
查询优化器,计算 SQL 对应的 IO 成本和 CPU 成本, 对 SQL 生成执行计划,确定执行路径,重组SQL
Cache&Buffer
执行路径
引擎层
: 真正负责Mysql
中数据的储存和提取,对物理服务器的底层数据执行操作,一切的逻辑处理和读取写入都只处理操作buffer poll
, 会将旧值记录到Undo Log
中储存层
数据库缓冲池 buffer poll
InnoDB
缓冲池包括了数据页、索引页、插入缓冲、锁信息、自适应索引 Hash
和数据字典信息等, 缓存池大小有限,会根据 位置×频次
, 优先对频次高的进行缓存
当 InnoD B
发现某些查询频繁访问特定的数据页时,会在内存中为这些数据页创建哈希索引。
title:缓存池操作
```java
查看/设置缓冲池的大小
show variables like 'innodb_buffer_pool_size';
set global innodb_buffer_pool_size = 268435456;
查看有几个Buffer Pool 实例
show variables like 'innodb_buffer_pool_instances';
默认一个,设置为多个则平分上面设置的缓存池大
需要缓存池至少1g以上设置多个才有效
高并发多个可能更好
[server]
innodb_buffer_pool_instances = 2
缓存池 (属于内存): 数据的修改会在缓存池中进行
非缓存池的内存部分: 表结构、索引信息等元数据存储, 数据库连接的状态和变量信息, 临时表
查询数据
先查缓存池,没有则查磁盘,然后加载到缓存池 (如果还没有刷会)
更新数据
先查缓存池,没有则查磁盘,然后加载到缓存池并修改更新,修改后的数据页会被标记为 脏页
(表示这部分数据在内存中已被修改,但尚未写回到磁盘)
- 脏页在后续的
某个时刻
会被刷回到磁盘。刷回的时机可能包括:(如果此时宕机将会永久更新)
- 缓冲池达到一定的阈值(如脏页数量超过一定比例)。
- 事务提交时,
MySQL
会将相关的脏页刷回磁盘,以确保数据的持久性。 - 定期的后台任务(如定时刷新)会将脏页写回磁盘。
刷写的过程是通过将脏页的内容写入磁盘,确保数据在物理存储中保持一致性和持久性。
JOIN
为了获取另一个表中和当前表相关的数据,设计出了 JOIN
LEFT/RIGHT
就是保留一张表 A的信息和另一张表 B信息中符合 ON
条件的,不符合条件的,A 中也会有 B 表的字段,但是为 NULL
JOIN
就是两张表合并,按照 ON
条件保留另一张表的数据
优化器会把上面的转换为下面的
内连接(INNER JOIN)(JOIN) ON xxx;
JOIN 表 USING(字段);
: 同内连接, 使用字段和类型
相同的情况可以使用,无需再写ON
后的条件, 会自动去除USING(列名)
的列名Nature JOIN;
相当于内连接,无需写任何条件,会自动设置字段相等的部分为条件, 会自动去除重复列名左连接(LEFT JOIN xxx ON xxx);
: 保留左边表独有数据保留,和右边表交叉中符合条件的数据全外连接FULL OUTER JOIN
(mysql
不支持), 采用UNION AL
交叉连接CROSS JOIN
(笛卡尔积)假设table1
有 3 行,table2
有 4 行,结果集将包含 3 交叉连接不需要条件,所有的行都会被组合。UNION ALL
但UNION
会去重导致效率低, 一般不推荐用JOIN ... USING
是为了简化字段的展示,去除重复字段
,而不影响结果集
的行数。UNION
和UNION ALL
则是操作结果集
的行,前者对两个表的相同字段的结果集去重,后者保留所有行。
数据类型
[[…/…/…/IT/Mysql/Mysql基础#数据类型|Mysql/Mysql基础 > 数据类型]]
char
: 255
, 固定,可能比 varchar
快 50%
varchar
: 65532 ,时间换空间
任何字段如果为非负数,必须是 UNSIGNED
数类型为 DECIMAL
,禁止使用 FLOAT
和 DOUBLE
- 【强制】如果存储的字符串长度几乎相等,使用 CHAR 定长字符串类型。
- 【强制】
VARCHAR
是可变长字符串,不预先分配存储空间,长度不要超过5000
。如果存储长度大于此值,定义字段类型为TEXT
,独立出来一张表,用主键来对应,避免影响其它字段索引效率。-阿里巴巴《Java
开发手册》
DATETIME
和TIMESTAMP
的异同
储存时间格式相同,都可以储存微秒,但是 timestamp 范围到 2038 年
储存表情
默认的 utf8mb3
改为utf8mb4
delete、drop与 truncate 的区别
DELETE
: DML
删除特定记录,支持事务,可恢复。
- 功能:用于从表中删除特定的记录。
- 语法:
DELETE FROM table_name WHERE condition;
- 效果:
- 仅删除符合条件的记录。
- 可以使用
WHERE
子句来限制删除的范围。 - 支持
事务
,允许回滚操作(在支持事务的存储引擎中,如 InnoDB)。 - 删除操作会在日志中记录每一行的删除,适合细粒度控制。
- 使用场景:当需要删除部分数据而保留其他数据时使用。
DROP
:DDL
删除速度快, 删除整个表或数据库,无法恢复。
- 功能:用于删除整个表、视图或数据库。
- 语法:
DROP TABLE table_name;
- 效果:
- 删除整个表及其结构和数据。
- 不能恢复,所有相关的
约束和索引
也会被删除。
- 使用场景:当需要完全移除一个表或数据库时使用,且不再需要任何数据或结构。
TRUNCATE
:DDL
快速清空表中的所有记录,保留表结构,通常不可恢复。
- 功能:用于快速删除表中的所有记录。
- 语法:
TRUNCATE TABLE table_name;
- 效果:
- 删除表中的所有记录,但保留表结构。
- 不支持
WHERE
子句,不能选择性删除。 - 通常比
DELETE
快,因为它不会逐行记录删除,而是直接释放数据页。 - 在某些情况下,
TRUNCATE
可能不会触发DELETE
触发器。 - 不能在事务中回滚(在大多数情况下)。
- 使用场景:当需要快速清空表中的所有数据而不关心逐行删除的细节时使用。
金钱
定点数 : decimal(x,y)
x 代表位数,y 代表小数点后的数
count (*),count (1),count (列名)
count(*)
不忽略 Null
count(1)
不忽略 Null
count(列名)
忽略 Null
count (主键)>count (1)>count(列名)>count (*)
如果只有一个字段, count(*)
最快
[[…/…/…/IT/Mysql/Mysql高级#10 11 2 COUNT 与 COUNT 具体字段 效率|Mysql/Mysql高级 > 10 11 2 COUNT 与 COUNT 具体字段 效率]]
引擎对比
页结构
缓存池加载页的三种方式
- 内存读取
- 随机读取
- 顺序读取
![[…/…/…/IT/Mysql/Mysql内部结构#^vep6gv]]
为了避免随机读取,设计了区
页 (16 kb)(I/O和磁盘交互的最小单位) 叶子节点页/非叶子节点页 (目录页)
也就是一次最少读取 16kb
到内存中
–> 区(最多1MB=64个连续页×16kb)(最少两个区[叶子/非叶子])(避免页物理位置不同导致随机IO)``碎片区:最少两个区2M,浪费空间,后来引出了碎片区,不独立属于任何一个段
–> 段(叶子节点区/非叶子节点区)(同类型的区被称为段)(数据段, 索引段, 回滚段)
虽然 MySQL 的 InnoDB 存储引擎在设计上倾向于将数据页物理上连续存储以优化性能,但由于数据的动态性、删除、更新等操作,实际存储中可能会出现不连续的情况。因此,物理连续性并不是绝对保证的。
- 索引分配空间
就不再按照页, 而是区, 数据量大可能分配很多个连续的区 (不能保证全部页是连续的,但是能保证一个区中的页物理上连续,避免了随机 IO, 区之间不一定连续,采用双向链表连接)
页内部结构
- 数据页
数据页分为
文件头File Header
(页号/上下页号,页类型, 校验和[发现异常进行回滚])
文件尾File Trailer
(校验和,发现异常进行回滚,尾部的校验和可以提供额外的保护)
页头Page Header
(比如本页储存了多少条数据, 第一条记录的地址等)(页目录的槽数量)
本页最大最小记录
Infimum小+Supremum大
用户记录
(每条记录之间由单链表连接) 按照指定的行格式进行存储 [[…/…/…/IT/Mysql/Mysql内部结构#COMPACT 行格式]]
空闲空间
会从空闲空间申请到 用户记录
页目录
(一个页内有很多数据,为了快速查到数据,页内也需要页目录) 将 4-8
个一组,每组只取出一个最大 (第一组取最小)
用户记录
:InnoDB
行格式: Compact
, Redundant
, Dynamic
, Compressed
-
Compact行格式
: 插入的每一条记录都有一个
变长字段的长度列表
null 值列表
(记录每列的值是否为null
)(1 代表为NULL
,0 不为NULL
)记录头信息
:
delete_mask
记该条记录是否被删除, 0 未删除 1 代表删除 (有被标记为垃圾的组成一个所谓的垃圾链表,称为可重用空间,新数据可能覆盖删记录的空间)
min_rec_mask
是否为非叶子节点中的最小的记录 (B+树的每层非叶子节点中的最小记录都会添加该标记,标记为 1)
heap_no
该条记录在页中的位置
n_owned
页目录中每个组的最后一条记录有这个值, 代表该组多少数据
record_type
记录类型 0 表示普通记录、1 目录项、2 表示最小记录、3 表示最大记录 (是最大还是最小都是依据主键,或者自己定的)
next_record
下一条记录的字节偏移数
隐藏列
[[…/…/…/IT/Mysql/Mysql内部结构#隐藏列]]row_id/transaction_id/roll_point
-
页与页之间 :双向链表,便于跨页的遍历。
-
页内记录 (行格式) 之间 :单向链表,便于在同一页内的记录遍历。
-
页目录 [[…/…/…/IT/Mysql/Mysql内部结构#页目录]]
页目录存储 槽位
, 每组最大的值
因为页内记录是单链表,需要遍历,插入,删除效率高,但检索效率低,因此专门做了页目录来用二分法快速查找一个页中的数据
操作 | 单向链表 (Singly Linked List) | 双向链表 (Doubly Linked List) | 说明 |
---|---|---|---|
遍历 | O (n) (单向) | O (n) (双向) | 单链表只能从头遍历;双向链表可以从头或尾遍历。 |
插入 (已知位置后) | O (1) | O (1) | 找到插入位置后,操作本身时间复杂度为 O (1)。 |
插入 (已知位置前) | O (n) (需要查找前驱) | O (1) | 单链表需要在插入位置前寻找前驱节点,双向链表可以直接访问前驱节点。 |
删除 (已知位置节点) | O (n) (需要查找前驱) | O (1) | 单链表需要在删除节点前寻找前驱节点,双向链表可以直接访问前驱节点。 |
检索 (查找特定值) | O (n) | O (n) | 都需要线性搜索,逐个比较节点的值。 |
内存占用 | 较低 | 较高 | 双向链表需要额外的指针存储前驱节点,因此内存占用更大。 |
实现复杂度 | 简单 | 复杂 | 双向链表需要维护更多的指针,代码实现较为复杂。 |
假如一个页有 1000 条记录,内部也要有页目录,而且 1000 条全部拿来做页目录太大,将 4-8 个一组,每组只取出一个最大 (第一组取最小),每组取出的一个放到一个槽位 slot
中,只需要最多 250 个槽位,然后再对所有槽位进行二分法
第一个槽位 (最小记录): 对应一条记录 (只有一条通常用于确保在查找时有一个有效的起始点。其存在是为了方便范围查询和顺序遍历, 结构优化目的)
中间的槽位 (最大记录): 均对应 4-8 条记录
最后的槽位 (最大记录): 对应 1-8 条记录
insert into table (x,x,x) VALUES (x,x,x) where
update table set x=x x=x x=x where
delete FROM table where xxx
索引
索引就是表的数据结构, B+树数据结构,Navicat 看到的数据就是通过访问聚簇索引也就是 B+树结构看到的
为了帮助 mysql
高效获取数据的数据结构
InnoDB
索引的三次迭代成 B+树
, 就是对目录页结构进行了增加
索引是在 储存引擎
中实现的,储存引擎可以定义表的 最大索引数
和 索引长度
功能分类: 主键/普通/唯一性/单列/多列/全文
使用索引的地方
select
后需要的数据先 where 再 group by 再 having 再 order by 最后 limit
的位置
优点
: 降低 IO/保证数据库每一行数据的唯一性/减少查询中分组和排序的时间
缺点
: 创建和维护索引消耗时间/索引占用磁盘空间/降低更新表的速度, 一个表上建立的索引越多,占用越多的储存空间,增删改的性能就越差/索引个数一般不能超过六个, 超过时表数据更改的同时,索引也会进行调整和优化,造成负担,优化器会选择实际的索引,多个索引时,会增加 sql 优化器生成执行计划的时间,降低查询性能
[[…/…/…/IT/Mysql/Mysql内部结构#聚簇索引 索引即数据 数据即索引|Mysql/Mysql高级 > 聚簇索引 索引即数据 数据即索引]]
聚簇索引/主键索引``索引即数据/数据即索引
每个表都有一个聚簇索引,包含表中所有数据,也就是说表自身构成了聚簇索引
聚簇索引
并不是单独的索引类型,默认是按照 主键
构建,如果没有定义主键,自动将 unique
字段作为主键,如果还没有,会系统自动提供一个隐藏的字段 [[…/…/…/IT/Mysql/Mysql内部结构#隐藏列|Mysql/Mysql内部结构 > 隐藏列]]来构建索引,所有的用户记录都在聚簇索引的叶子节点
-
(二级索引)非聚簇索引/联合索引
联合索引顺序要满足sql
执行顺序才能利用上
先where
再group by
再having
再order by
最后limit
-
聚簇对比非聚簇
从 CURD 考虑
聚簇增,改性能较差 :增,改 可能导致数据的重排,影响性能,尤其是在大量数据插入时聚簇索引是按照主键,也就是真实物理排序存储的,而非聚簇是根据某个字段的排序, 如update
时 C3 时聚簇索引需要改,而非聚簇索引可能就没有存储这个字段不需要改
非聚簇, 不影响数据的物理存储顺序,插入和更新操作相对较快。可以根据不同的查询需求建立不同的非聚簇索引,优化特定的查询。
Mysql8
支持创建降序索引,支持隐藏索引 (有时索引创建需要消耗大量系统资源,有时需要测试有无索引的区别,直接删除后可能又需要创建消耗大量资源,因此支持隐藏索引)
适合索引
: UPDATE
、DELETE
的 WHERE
条件列/Group By 的
/ DISTINCT
字段/多表 JOIN
的连接字段/ limit
字段/数据类型小的 (节省空间)/字符串前缀 (前缀索引)(一部分)/最左前缀 (最频繁的放到左侧) /限制/
不适合建索引
: 重合数据多与 10%
的/用不到 where
的字段,经常更新的表创建过多索引/无序值/很少使用的/重复定义的/少于 1000 个数据的
对于 limit
,要避免如 LIMIT 1000, 10;
数据库也并不知道第 1000
条数据什么时候开始 (采用书签或者自连接优化)
![[…/…/…/IT/Mysql/assets/Mysql高级/为什么程序员对这8种SQL的写法零容忍 (09_12_2023 16_30_06).html|assets/Mysql高级/为什么程序员对这8种SQL的写法零容忍 (09_12_2023 16_30_06).html]]
性能分析工具使用
磁盘 IO>内存 IO>算法时间复杂度
索引减少的是磁盘的 IO 次数,比时间复杂度 (内存上) 高上一个级别 ; 假设磁盘交互一次耗时 200 ms, 而时间复杂度只耗时 4 ms
SHOW STATUS
查看 MySQL 服务器的状态和配置。
主要用于监控一些基本的性能指标,如连接数、查询数、锁等待等。
上次查找开销 : SHOW STATUS LIKE 'last_query_cost';
如果是顺序索引上查找,查找页数的影响小,耗时区别不大,因为顺序查找会一次性将页面加载到缓冲池中
mysqldumpslow
针对定位执行慢的 sql (默认 10s)
[[…/…/…/IT/Mysql/Mysql优化#慢查询日志分析工具: mysqldumpslow|Mysql/Mysql高级 > 慢查询日志分析工具: mysqldumpslow]]
Performance Schema
提供对 MySQL 服务器性能的详细监控
- 允许收集和分析多种性能指标,如锁、等待事件、I/O 等。
- 提供实时监控能力,适合长期性能分析和趋势观察。
explain
用于分析 SQL 查询的执行计划
EXPLAIN
可以查看优化器具体的执行计划是什么
8.0 后 explian
引入了 tree 形的 explain
输出FORMAT=tree
EXPLAIN DELETE FROM tabe where XXXX # 并没有真正删除,只是查看执行计划是什么
[[…/…/…/IT/Mysql/Mysql高级#EXPLAIN 各列作用|Mysql/Mysql高级 > EXPLAIN 各列作用]]
explain
各项
id/select_type/table/type/possible_keys/key/key_len/ref/rows/filtered/Extra
id
: 几个 select
就有几个 id
, 优化器会将嵌套重写为多表连接
select_type
: primary,union.subquery
不包含 union
的或者子查询的都算是 simple
-
type
:
system
表只有一行,一般是系统表,往往不需要进行磁盘 IO,速度非常快
const
使用主键或唯一索引进行只返回一行的等值匹配
eq_ref
被驱动表
是通过主键或者唯一二级索引列等值匹配的方式进行访问的
ref
当通过普通的二级索引列
与常量
进行等值匹配
时来查询某个表
fulltext
表示使用全文索引查找,适用于对文本字段的全文搜索
ref_or_null
当对普通二级索引进行等值匹配查询,该索引列的值也可以是NULL
值时
index_merge
表示使用多个索引进行查询,MySQL 会将多个索引的结果合并
unique_subquery
子查询返回唯一值时的优化,通常用于 IN 子查询。
index_subquery
似于unique_subquery
,但子查询可能返回多行。
range
只检索给定范围的行,使用索引
来检索。在where
语句中使用bettween...and
、<
、>
、<=
、in
等条件查询type
都是range
index
表示全索引扫描,虽然比全表扫描(ALL
)快,但仍然不是最佳选择,通常使用联合索引最后一个条件时会用到,index
,这表示MySQL
确实扫描了索引,但并不意味着查询有效地利用了索引。依旧算作索引失效ALTER TABLE `countries` ADD INDEX (country_id,region_id,country_name); Explain SELECT * FROM countries WHERE country_name='Canada';
ALL
效率最低的全表扫描
system > const > eq_ref > ref > range > index > ALL -
possible_keys
列:可能会用到的索引,但并不一定实际被使用 -
key
实际使用的索引。如果为NULL
,则没有使用索引。 -
key_len
MySQL
决定使用的索引长度(以字节为单位), 实际使用到的索引长度, 便于检查是否充分的利用上了索引
, 相比自身数值如varchar
范围的值越大越好
, 主要针对于联合索引,有一定的参考意义, 当表有多个索引可用时,key_len
字段可以帮助识别哪个索引最有效,是否充分里利用索引
EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a';# KEY_len=303
EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a' AND key_part2 = 'b'; # KEY_len=606
EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a' AND key_part2 = 'b' AND key_part3 = 'c'; # KEY_len=909
-
ref
列:用于与索引列比较的值来源 -
rows
预估的需要读取的记录条数值越小越好
-
filtered
某个表经过搜索条件过滤后剩余记录条数的百分比,决定了被驱动表要执行的次数,rows
乘filtered
得到满足条数 -
Extra
查询执行的附加信息Using index``覆盖索引
use where
普通where
使用索引的where
不提示, 为Null
WHERE
条件永远为false
式会提示impossible WHERE
using MRR(默认关闭)
对二级索引范围查询时,先对二级索引的查询结果缓存并排序,然后统一去聚簇索引中回表查询
索引覆盖: 不用回表就能查到想要的数据,叶子节点中正好有这两个数据,也就是说 select
中的数据正好在索引中
ALL
越往前效率越高,后三个效率最低
索引失效
索引失效指的是查询未能有效利用索引而导致性能下降
type
为 Index
就算是索引失效
聚簇索引
: 物理存储按照索引排序
非聚集索引
: 物理存储不按照索引排序
[[…/…/…/IT/Mysql/Mysql高级#索引失效案例|Mysql/Mysql高级 > 索引失效案例]]
-
(+,-,!=)
和函数
(mysql
也不不知道是什么函数,会将数据一个一个取出,进行函数操作) -
最左前缀原则
-
类型转换
(自动或手动) (可能会导致数据库系统无法使用索引,从而导致全表扫描, 行锁升级为间隙/临间锁,导致卡住) -
联合索引中使用范围的字段,该右边的字段索引失效 (并列
and
, 或where
后的Order by
)(右边的字段不能继续使用上索引),但是声明范围的字段本身可以使用索引,如果范围字段比较影响性能,就考虑使用范围,牺牲
其他字段的索引,有时范围上创建索引能大大减少数据量,尽可能让使用索引的过滤掉更多数据 (and 左右顺序优化器可以优化,但索引顺序是固定的)(或者将声明范围的字段设置为联合索引的最后一个字段,则就都能用上索引了)
声明范围的是联合索引最后一个字段 -
以通配符
%
开头的索引失效,严禁左/全模糊
-
OR
前后 存在非索引 的列, 索引失效, 必须左右两个都有索引 -
升序降序
要想使用索引需要保持一致 -
左/右连接
关联查询的字段编码格式
不一样,连接田间的字符编码格式不一样,可能导致索引失效, 数据库和表的字符集统一使用utf8mb4
,统一字符集转换产生的乱码,不同的字符集
进行比较前需要进行转换
会造成索引失效 -
is not null(可能)
是不等于某个值 , 导致索引失效 (回表次数太多,数据量太大,成本太高,优化器会强制执行全表),is null
可以使用索引 , 最好设计式将字段设置为not null
约束 -
数据量小,优化器会选择不用索引
Mysql
优化器估计使用全表扫描要比使用索引快 , 则不使用索引
and
左右的语句顺序不会影响联合索引,优化器会自动对语句进行排序
Order BY
中的字段如果进行升序或者降序,需要所有字段保持一致才能用上索引
有时不会全部索引都用,优化器优化可能会觉得没必要使用第二个索引
另外,SQL 语句是否使用索引,跟数据库版本,数据量,数据选择度都有关系
Join
优化
simple/index/block/ Nested-Loop join
![[…/…/…/IT/Mysql/Mysql优化#索引优化与查询优化|Mysql/Mysql优化 > 索引优化与查询优化]]
有时外连接会被优化器改为内连接
对于内连接
如果都没有索引: (小的结果集驱动大的结果集)
如果只加一个索引, 有索引的字段所在的表会被作为被驱动表
如果都存在索引, 优化器会自己决定谁作为驱动, 会选择数据少的作为驱动表, 数据多的作为大表, 自动选择成本低 (小表驱动大表)
simple/index/block/ Nested-Loop join
simple
: 被驱动表 (非驱动表)加载到内存中,驱动表对比一个数据就清除了,下次还要重新加载
index
: 被驱动表上 有
索引,驱动表对比索引,然后回表得到数据, 两张表都有索引也是 index
block
: 驱动表没有
索引时, 一次性加载驱动表的一些数据,这样被驱动表就不需要的加载一次就被清除了,相比 simple 节省了大量资源
具体使用哪种连接算法取决于查询执行计划和优化器的决策
子查询优化 (临时表)
避免使用子查询,因为 mysql
会通过建立 临时表
来完成子查询,查询完成后再撤销这些表 , 消耗过多的 CPU
和 IO
资源,而且建立的临时表不会存在任何索引
通过 Join
查询可以代替子查询,不需要建立临时表,再加上索引, 比子查询要快的多
Order By
排序优化
[[…/…/…/IT/Mysql/Mysql高级#排序优化|Mysql/Mysql高级 > 排序优化]]
Extra
中 Using index/Using FileSort
Using index
不进行排序, 使用上了索引,索引已经保证了有序性
Using FileSort
在内存中排序, 要额外的磁盘 IO 和排序处理的时间, 占用 CPU 高,并不一定是最慢,案例中比 using where
快
在 WHERE
上使用索引是为了避免全表扫描
在 Order BY
上使用索引是为了避免 FileSort
排序,Index
排序的效率高
数据量过小 Group By
就不会使用索引, 或者只用联合索引的一部分
UsingFileSort
并不一定是最慢,如果 where
过滤完数据量很小,可能比 index
还要快
Order BY
中的字段如果进行升序
或者降序
,需要所有字段保持一致才能用上索引,如果排序的数据行超过这个参数设置的长度,MySQL
会使用基于磁盘的排序算法(例如使用临时文件),否则会使用内存中的快速排序。
title:简介



`name` 也被用上




title: `filesort` 算法:双路排序和单路排序
- 当可用内存足够大以容纳所有待排序数据时,MySQL 会使用单路排序。
- 当数据量超出可用内存时,MySQL 将采用双路排序。

GROUP BY
优化
group by
使用索的原则几乎跟 order by
一致,group by
即使没有where
过滤条件用到索引,也可以直接使用索引。
实在无法使用索引时,调整参数,增大 max_length_for_sort_data
和 sort_buffer_size
参数的设置,分别影响排序数据的处理和排序缓冲区的大小。
分页查询优化
优化考虑索引, 将 LIMIT
操作转化为可以使用上主键索引的操作
- 书签方式优化: 使用上一次查询的位置
- 使用索引
- 先
order by
/where
减少数据量后再Limit
自连接
优化
换成等值连接

往索引上靠


索引覆盖
索引覆盖特指使用非聚簇索引(如辅助索引)来满足查询的条件, 索引中包含查询需要的全部数据
避免回表造成二次查询,效率降低,把随机 IO 变成了顺序 IO
但需要人员来维护索引字段
EXPLAIN SELECT * FROM student WHERE age = 20
用上索引但是回表,会快速通过索引找到满足 age 为 20 的主键id,然后再根据主键进行回表从聚簇索引查找
title:优先考虑覆盖索引 (不需要回表)
> 利弊



索引下推 (ICP: index condition push)
Extra:Using index condition
两个查询条件都是同一个字段
时,当另一个索引失效时, 再进行查询时 , 不再回表,直接用第一个条件查出的数据进行过滤
title:索引下推




- 索引下推开启关闭

- `ICP` 使用条件

聚簇索引不需要回表不需要回表的都用不了 `ICP`
其他
[[…/…/…/IT/Mysql/Mysql高级#其他查询优化|Mysql/Mysql高级 > 其他查询优化]]
EXISTS
和IN
的区分
in
语句是把外表和内表作 hash
连接,
exists
语句是对外表作 loop
循环,上面的 join
,循环
- 如果查询的两个表大小相当,那么用
in
和exists
差别不大。 - 如果两个表中一个较小,一个是大表,则子查询表大的用
exists
, 子查询表小的用in
not in
和not exists
:not in
, 没有用到索引,那么内外表都进行全表扫描,not extsts
的子查询依然能用到表上的索引。所以无论那个表大,用not exists
都比not in
要快
索引下推
(默认开启)
SQL 中两个查询条件都是 同一个字段
时,一个查询条件能用上索引,而另一个查询条件不能用上索引,另一个不能用上索引的查询条件, 无需再回表,直接用第一个使用上索的查询条件的数据
普通索引 vs 唯一索引 change buffer
普通索引 vs 唯一索引 ,两者在查询能力上没有差别,唯一索引
的更新不会使用 change buffer
, 如果主要考虑的是对 更新性能
的影响,建议普通索引,可以和 change buffer
来配合使用,对数据量大的表更新优化比较明显,如果在更新后面都马上伴随着查询,就应该给关闭 change buffer
,而其他情况下 change buffer
可以提高性能(更新缓存操作会缓存到 change buffer 中,避免了从磁盘重新读入)
Change Buffer
是一个内存结构,它用于存储对二级索引的修改(如插入、更新和删除)而不立即将这些更改写入磁盘。这样可以减少对磁盘的 I/O 操作,从而提高性能。
总结 : 经常修改 : 普通索引
title:普通索引 vs 唯一索引




普通索引查找到符合条件的记录后会继续查,知道碰到不符合的记录,对于唯一索引,查到符合记录的就不会往下继续查了
change buffer
: 当要更新一个数据页的时候,如果页在内存就直接更新,没有就会更新到 change buffer
中,避免直接从磁盘读取数据,在 change buffer
中执行和页有关的操作,然后系统后台会有线程定期 merge
,也就是更新到原数据页中 ,数据库关闭时也会 merge
,通过 change buffer
,语句的执行速度会得到显著提升,还能通过这种方式避免读入内存占用 buffer pool
LIMIT 1
对优化的影响
对于普通索引 : 如果知道结果只有一条,这样获取到一条结果就不会继续进行扫描了, 会加快速度
对于唯一索引 : 没有区别
commit
commit
可以提高性能
释放资源 : 回滚段用于恢复数据的信息/被程序语句获取的锁/ redo/undo log buffer
中的空间
淘宝数据库,主键如何设计的?
淘宝数据库,主键如何设计的?
并没有使用主键 ,因为不可靠,不安全 ,性能查 ,交互多,不具有全局唯一性
淘宝订单 ID = 时间+去重字段+用户 ID 后 6 位尾号
[[…/…/…/IT/Mysql/Mysql高级#淘宝数据库,主键如何设计的?|Mysql/Mysql高级 > 淘宝数据库,主键如何设计的?]]
不适合索引时
where
用不到的字段/数据重复率高,不能快速定位/数据量小的表 (小于 1000),索引创建意义不大/数据经常更新的表/无序字段/删除很少用的索引/冗余重复索引
总结
优化就是通过 explian
分析当前 sql
语句, 针对 where
/ join
/ 排序
/ GROUP BY
/ limit
这些语句分析是否有可优化的空间, 查看索引使用情况,使用了多少索引,是否索引覆盖 (无需回表, 随机 IO 变成了顺序 IO) , 避免索引失效
如果是 join
要看 filtered
索引覆盖: 不用回表就能查到想要的数据
where
效率高于 having
,能写在 where
限定的条件就不要写在 having
中了
减少 order by
,排序放到程序端去做
含了 order by、group by、distinct
这些查询的语句,where 条件过滤出来的结果集请保持在1000行以内,否则 SQL
会很慢
有哪些方式优化慢 SQL
-
慢查询日志
:开启 MySQL 的慢查询日志,再通过一些工具比如mysqldumpslow
去分析对应的
慢查询日志,当然现在一般的云厂商都提供了可视化的平台。 -
服务监控
:可以在业务的基建中加入对慢 SQL 的监控,常见的方案有字节码插桩、连接池扩展、ORM
框架过程,对服务运行中的慢SQL进行监控和告警。
范式
减少数据冗余,提高数据完整性和一致性,并使数据库结构更加清晰。
[[…/…/…/IT/Mysql/Mysql高级#范式]]
第一
: 每个字段具有原子性,不可拆分
第二
: 满足第一范式的基础上,满足数据表中每一条数据记录都是可标识的,所有非主键字段必须完全依赖主键,不能只依赖主键的一部分,也就是必须要用上所有主键字段才能推导出主键,才算第二范式
(姓名, 学号,课程号–>成绩) 满足
(姓名, 学号,课程号,性别–>成绩) 不满足
第三
: 在第二的基础上,确保每个主键字段都和主键直接相关
反范式化 : 一般第一范式遵循,第二第三不遵循,比如专门设计冗余字段
什么时候考虑冗余字段 : 这个字段查询不可或缺,不需要经常进行修改
ER
模型
表的个数/字段/联合主键的字段越少越好
使用主键和外键越多越好
事物
事物是 一组逻辑的操作单元
,是数据从一个状态转变成另一个状态
最早起源于金融行业,后来计算机需要数据一致性操作时,自然而然的进行了引用
ACID
特性atomic , consistency ,isolation, durable
原子性
(基础) : 事物状态全部保持一致,要么全部提交,要么全部失败回滚 (例如,程序崩溃、死锁等, UNDO LOG
回滚日志实现,如果事务中途失败或被显式回滚,系统可以通过 UNDO LOG
恢复到事务开始前的状态。)
一致性
(约束) : 事务执行前后,数据从一个合法性状态转变成另一个合法状态,一致性保证事务的执行不会违反数据库的完整性约束 (UNDO LOG
回滚日志实现 UNDO LOG
会确保所有的修改都被撤销,从而使数据库返回到事务开始前的一致性状态)
隔离性isolation
(手段): 一个事物的执行过程中不能被其他事物干扰 ( 锁机制
实现)(隔离级别)
持久性
(目的) : 事物一旦被提交,对数据库的数据改变就是永久的 (REDO LOG
重做日志实现,系统在事务提交后立即崩溃,REDO LOG
也可以用来重做这些操作)
undo
日志确保了原子和一致性,redo
确保了持久性
事物的状态
活跃的/部分提交的 /失败的/中止的/提交的
Mysql事物并发问题
mysql 8.0
默认的隔离级别为 可重复读
- 可重读读隔离级别
写-写
: 写锁解决
读-写
: 不加锁, MVCC 快照读(多版本并发控制),不影响写,可以同时读写,但可能存在数据不一致问题,因此某些业务必须要手动加锁
在可串行化隔离级别下,大多数情况下不需要手动加锁,因为数据库会自动处理。
许多现代数据库系统(包括MySQL
的InnoDB
存储引擎)采用了MVCC
来更好地处理并发事务。这并不意味着读写锁不再有用,而是说MVCC
在特定场景下提供了更优的解决方案。
- 多版本并发控制
MVCC
: 就是生成ReadView
来找到历史版本 (由 undo 日志 ) , 不同隔离级别控制不同,也就是生成ReadView
的时机不同READ COMMITTED
: 每次执行SELECT
操作时都会生成一个ReadView
, 读也就是select
只会看到生成ReadView
之前已经提交事物所做的更改 , 之后或者未提交的看不到 ,解决了脏读
REPEATABLE READ
: 一个事务在执行过程中只有第一次执行SELECT
操作才会生成一个ReadView
,在本事物结束前,之后的SELECT
操作都复用这个ReadView
,这样也就避免了不可重复读和幻读的问题
不能保证获取数据是最新的, READ COMMITTED
相对 REPEATABLE READ
更新一点
脏写 > 脏读 > 不可重复读 > 幻读 (都是 隔离性
没有保证所引发的问题)
读未提交>读已提交>可重复读>串行化
当前读
是指事务在执行时直接读取当前数据库的最新数据版本。它通常需要获取锁(如排他锁或共享锁),以确保读取数据时的准确性和一致性。
快照读
是指事务读取某一时刻的数据版本,而不需要加锁。它通过 MVCC
机制提供了一个数据的快照,允许事务在不干扰其他事务的情况下读取数据。
脏写
: 事物 B修改了事物 A 正在修改的数据, 事物 A 回滚导致事物 B 修改失效
解决: 读未提交
,一个事务在进行读取操作时可以不获取任何锁,因此其他事务仍然可以并发地进行当前读,会导致脏读,但是写会加锁来避免脏写
在 “读未提交” 隔离级别下,事务可以读取其他未提交事务所做的更改,即使这些更改未在 MVCC 中正式提交。
MVCC
版本:MVCC
的设计是为了管理已提交的数据版本,因此在更高的隔离级别(如 Read Committed, Repeatable Read, Serializable
)下,事务会读取 MVCC
中的已提交版本
脏读
: 一个数据, 一个 事务A
修改了数据,还不确定会不会回滚时或者还没有处理完成数据,另一个 事务B
就访问了该数据 , 此时访问读到的数据是脏数据 (Dirty Data)
, 事务A
可能 roolback
导致读到的数据无效
解决
: 读已提交
: 一个事务只能读到其他事务已经提交的数据,未提交则读到提交前的数据,而不可重复读是其他已提交事务的数据也读不到
mysql落地解决方式
:读已提交
每次读都生成一个 Readview
, 这样只能读到已提交的快照,不会读到正在修改的数据,写的话上写锁
不可重复读
: 一个数据, 一个事务A
多次读取,另外一个事务B
也读取并修改了这一数据,导致第一个事务 A多次读取到不同结果
理论解决方式
: 可重复读
: 在 自己事务过程中
多次读取的数据是一致的,在自身事务多次读取过程中都不受到其他事务的影响,另一个事务已经提交了也读取不到(MVCC)/让另一个事务无法修改(锁)
mysql落地解决方式
:MVCC、锁机制(MVCC 效率高,经常用于读的时候, MVCC 读的时候不会上读锁,需要写时会用锁)可重复读
只在第一次读取生成一个 Readview
, 保证不会读到其他事物的, 但不能保证数据的时效性,另一个事务可能在读取中修改,导致数据过时,依旧需要手动获取锁来处理,避免其他事务的影响,另外配合 临键锁``间隙
避免一部分幻读
幻读
: 事物 A
自身读取一个范围内的数据后, 另一个事物插入了新数据,造成两次读到内容不一致
查询特定一行, 虽然第二次没有比第一次 多出
新插入的数据,但插入相同数据会失败 (如果少了数据叫 不可重复读
)
解决方案: 可串行化: 完全使用锁不再使用 MVCC
,会在读的时候加锁,
spring
中的事物
springboot
启动文件上开启事物管理,添加注解 @EnableTransactionManagement
注解 @Transactional
,标注在 service
层的 方法,类,接口
上
- 只读
@Transactional(readOnly = true)
- 超时回滚,释放资源
@Transactional(timeout = 3)
- 回滚策略
rollbackFor
/rollbackForClassName
/noRollbackFor
/rollbackFor
- 隔离级别
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化
- 传播行为
propagation
^sz3dj3
A 调用 B 方法时, A 有无事物 B 的传播设置
[[…/…/…/IT/JAVA微服务核心/JAVAWEB/JavaWeb#传播行为 propagation|JAVA微服务核心/JAVAWEB/JavaWeb > 传播行为 propagation]]
required
/ required_new
/ supports
/ not_supports
/ never
/ mandatory
/ nested
A 调用 B 方法时
required
: B 需要事物
A 有事物,则 B 加入 A
A 没有事物 , B 则自己创建一个事物required_new
B 需要新事物
A 有事物则挂起,B 新事物会优先保证自己运行supports
B 支持事务,A 可以无事务
A 有事物则加入,无事物就无所谓not_supports
B 不支持事物
A 调用无事物 B, A 有事物 B 则挂起never
A, B 都要无事物
A 调用 B, A 也必须无事物,否 d则异常mandatory
A 调用 B, A 必须有事物,否则异常nested
A 调用新事物 B, A 有事物作为嵌套事物存在,A 没事物以新的事物执行
日志
undo(前) | redo(后) | binlog | |
---|---|---|---|
作用 | 原子性、一致性 | 持久性(根据策略不同,刷入频率不同) | 同步数据,影响从库数据 |
触发场景 | 需要回滚时(死锁,事务失败) | 系统崩溃时 | 数据恢复、复制和审计 |
功能 | 记录修改前的数据 | 记录修改后的数据 | 所有数据库有关数据 |
范围 | 只影响本机数据 | 只影响本机数据 | 记录本机改动,影响 从机数据 |
redo/undo
binlog
数据修改 前
的样子 undo log
回滚日志
,作用是事务回滚,即当事务执行过程中发生错误或者用户主动回滚时,可以将数据恢复到事务开始之前的状态。undo log
还用于实现 MVCC,使得不同的事务可以读取到不同版本的数据。undo log 的主要作用是回滚未提交的事务,不能用于已提交事物的撤销,也就是人为错误代码执行无法恢复
数据修改 后
的样子 redo log
重做日志
如果操作人员失误错误的执行了语句,而与事务无关,就需要依靠 binlog
(变更历史查询/数据库主从/备份和恢复),redo log 只负责确保事务提交的数据不丢失,它并不会记录事务执行的语句。如果操作人员失误,执行了错误的语句,redo log 无法提供回滚操作到错误之前的状态。
- undo log 是 “后悔药”,在事务执行过程中生效;
- redo log 是 “保险箱”,在事务提交时生效,保证已提交事务的持久性
- binlog 是 “时光机”,在事务结束后生效,可以恢复到过去某个时间点的数据状态。
redo log
(数据恢复保证 持久性
) 重做日志
REDO LOG
主要用于在系统 崩溃
后恢复 已提交事务的修改
/确保事务的持久性
REDO LOG
不对外暴露,主要用于数据库内部的崩溃恢复过程
记录了对于 InnoDB
存储引擎的事务日志
redo log
: 为了保证持久化,我们只需要保证崩溃重启后能把改动恢复即可
比如,某个事务将系统表空间中第 10 号页面中偏移量为 100 处的那个字节的值 1 改成 2。我们只需要记录一下:将第 0 号表空间的 10 号页面的偏移量为 100 处的值更新为 2
redo
日志是顺序写入磁盘的
事务执行过程中,redo log
不断记录
日志占用的空间非常小
降低刷盘频率
undo log
日志 (保证 原子性
与 一致性
) 回滚日志
每条 SQL 通过执行器,就会立刻在回滚段 UNDO LOG
UNDO LOG
主要用于回滚事务
,以确保数据库的原子性和一致性。它记录的是每个事务在执行过程中对数据所做的修改之前的原始值。
作用 :
- 回滚数据
MVCC
[[…/…/…/IT/Mysql/Mysql高级#undo 日志|Mysql/Mysql高级 > undo 日志]]
binlog
和redo log
有什么区别
只有当一个事务 成功提交 时,MySQL 才会将这个事务期间的 所有逻辑操作 写入 binlog。
事物执行过程中不成功->回滚用到 undo log
也就是说事务执行过程中->不断写入 redo log
->但是只有事物提交后,崩溃才会用到 redo log
,事物没提交就崩溃则 undo log
事务提交时->写入 bin log
(因为没提交时写入没有意义,没提交说明没有执行完毕,可能发生故障依靠 undo log
即可) ->- 使用 binlog 将数据库恢复到过去的某个时间点(例如,发生误操作时)/主从复制时,从库通过读取主库的 binlog 来同步数据/用于审计数据库操作
bin log
是在事物提交后记录的,需要确保事物已经提交再记录,因此需要两阶段提交
Binlog
用于记录所有更改数据库的操作,包括 INSERT、UPDATE、DELETE
等。主要用于数据的复制和恢复,- 支持主从复制,可以用来恢复数据到某个时间点。
⼀条更新语句怎么执行的了解吗
一条 update
语句执行,先在 Undo Log
记录一个反向的记录,Undo Log
记录完成后,再进入 Buffer pool
中,对 Buffer pool
中数据进行修改 (这一步执行修改的命令),修改完后写入 Redo log Buffer
- 进入
prepare
阶段Redo log Buffer
写入redo Log
,标记为prepare
,binlog
会被写入到内存中的binlog cache
- 进入
commit
阶段- 刷盘写入
binLog
,写入完成后将Redo log
设置为commit
, 一次数据的写入就完成了. 只要binlog
写磁盘成功,就算redo log
的状态还是prepare
也没有关系,一样会被认为事务已经执行成功;
- 刷盘写入
先写入 redo log
, 后写入 bin log
关系型数据库(如 MySQL)通常都是执行命令之前记录日志(方便故障恢复),而 Redis AOF 持久化机制是在执行完命令之后再记录日志。
为何redo log 两个阶段提交
redo log
关注的是物理数据的变化,确保数据页的更新能够在崩溃后恢复。binlog
关注的是逻辑操作,记录的是如何从一个状态变更到另一个状态
binlog
记录的是逻辑操作
,即对数据库的具体操作,比如 SQL 语句或数据变化事件。
当你执行 UPDATE table_name SET a = 3 WHERE ...
时,binlog
会记录这个 SQL 语句,而不是物理数据的变化。
- 宕机情况
直接提交分为下面两种
先写入
redo log
, 后写入bin log
:
写完 binlog log
且 commit
后,就具有了 持久性
,
如果还没有 commit
就宕机,分为三种情况
binlog
尝试写入时发生失败,MySQL 检查发现没有数据,进行 undo log 回滚binlog
已经写完没来的及commit
就宕机,这时,MySQL 会去检查 binlog 中是否有这个事务的记录。
- 如果 binlog 中有这个事务的记录,说明第二阶段第一步成功了,此时MySQL认为这个事务是完整的,将这个事务的redo log设置为commit,并完成事务提交
binlog
: 写入一半发生宕机, MySQL 在启动恢复时,会检查 binlog 文件,并识别出哪些是完整的事务记录,哪些是不完整的。对于不完整的binlog
记录,MySQL
在恢复时会直接跳过。避免了从机数据不一致, 最后 mysql进行 undo log 回滚
- 为什么不删除 binlog:
- 性能考虑: binlog 是追加写入的,删除操作会非常复杂,涉及到文件结构的修改,效率很低。
- 容错考虑: 即使 binlog 中存在一些不完整的事务记录,这些记录在恢复时会被跳过,不会对数据一致性产生影响,因为只有事务成功提交后,从库才会同步这些完整的 binlog。
如果先写入 bin log
, 宕机后做出更新的数据不会被恢复, 但是,由于 bin log
中已经记录了这条更新操作,使用 bin log
进行数据恢复或备份时,会认为数据已经被更新过了,但实际没有更新,这个更新会被同步到从机,导致从机比主机多一条记录
第一阶段 prepare
: 用来确保宕机可以正确恢复,宕机发生在 prepare
之后,MySQL 可以根据 redo log
的状态来判断事务是否可以提交,或者需要回滚
第二阶段 commit
: 用来确保从机 redo log
是一致的, 保证了事务的 原子性(要么都成功,要么都失败)和 主库 binlog 完整性,进而间接保证了 主从数据一致性。
索引
redo log
怎么刷⼊磁盘的
[[…/…/…/IT/Mysql/Mysql高级#^5wu9ub|Mysql/Mysql高级 > ^5wu9ub]]
什么时候会刷入磁盘
log buffer
/事物提交时/后台线程输入/正常关闭服务器/ checkpoint
规则
⼀棵 B+树能存储多少条数据呢
存放数据量=叶子节点数 = 单个目录页存放记录数 × 单个子目录页存放记录数 ×叶子节点页存放记录数
如果假设目录页能存 1000 条目录,叶子节点能存储 100 条目录
三层就能存储 1000×1000×100 = 1 亿
为什么要⽤ B+ 树,⽽不⽤普通⼆叉树
为什么⽤ B+
树⽽不⽤ B
树呢
B+是 B 的加强版/扫描能力强,只需要遍历叶子/磁盘读写强大 ,IO 次数少,且 IO 次数稳定,意味着效率稳定/排序能力强 (数据之间是链表)
B+
所有数据都在最底层,效率稳定,且非叶子节点不装数据,能储存更多的目录信息,会连接更多的叶子节点,也就是可以储存更多的数据,且由于所有数据都在最底层,每个页之间采用双向链表连接 ,可以很方便的范围查询
一次 B+索引树的查找过程
title:简介




大表如何添加索引
由于创建索引时,是会对表加锁的,可能影响到正常业务,于是创建一个数据结构相同的表 B,创建好需要的索引,然后把大表的数据导入到表 B,然后表 B 名改为大表名,原大表换别名
hash
索引和 B+
树的区别
锁
脏写
: 修改了别的事物的数据
脏读
: (读取到另一个正在修改的数据事物)一个事务正在访问并修改数据,修改还没有提交到数据库时,另外一个事务也访问并使用了这个数据,读到的数据是脏数据 (Dirty Data),依据脏数据所做的操作可能是不正确的
解决
: 开启事物 A 没结束时,事物 B 查询读取到的值是事物 A 未开启时的值,即使已经修改,但事物 A 提交后再查数会变,不可重复读
不可重复读
: (事物自身读取后的数据被修改 )一个事务多次读
同一数据还没有结束时,另外一个事务也读取并修改了同一数据,导致第一个事务再次读取该数据时得到了不同的结果。
解决
: 查询依旧是未修改的值
幻读
: 事物自身读取后一个范围内的数据后另一个事物插入了新事物插入了新数据,造成两次读到内容不一致
查询特定一行, 虽然第二次没有比第一次 多出
新插入的数据,但插入相同数据会失败 (如果少了数据叫 不可重复读
)
行锁中的间隙锁/临键锁解决
[[…/…/…/IT/Mysql/Mysql高级#^5cjxde|Mysql/Mysql高级 > ^5cjxde]]
隔离级别 : 读未提交<读已提交 (脏读)<可重复读<可串行化
表锁
表级别的 S/X
锁
触发 : DDL
操作, drop table``alter table
/ 无法使用索引可能导致行锁升级为表锁
手动触发 : Lock Tables t READ/WRITE
在执行复杂的查询(如聚合、连接等)时,可能会对整个表加 S 锁,以确保查询的一致性。
执行数据定义语言(DDL)操作(如创建、修改或删除表)时,通常会加 X 锁,以确保结构更改的安全性和一致性。
表锁就是不对 DML
加锁,但 DDL
期间阻塞 DML
,同理 DML
阻塞 DDL
一般不会用到, InnoDB
表级别的 S 锁和 X 锁了解就可
意向锁 (意向共享锁 IS
/意向排他锁 IX
)
两者可以共存; 插入意向锁(行锁)
则表示有数据想要插入间隙 , 不同于 意向锁
触发: 任意隔离级别,任意读写
就是 标识锁
, 为了协调 行锁
和 表锁
的关系,支持两种锁并存
在表中某一行加 X锁
的情况下,其他事物不知道这个表中有没有 X/S锁
,需要一条一条数据进行遍历,效率低,因此加入了 IX/IS
意向锁来标识这个表中哪一行有 X/S锁 (排它锁)
, 以便快速判断
有 读意向锁
可以加表级 读锁
有 写意向锁
,表级 读写锁
都不能加,但可以加行级 读写锁
自增锁
[[…/…/…/IT/Mysql/Mysql高级#③ 自增锁 AUTO-INC 锁 了解|Mysql/Mysql高级 > ③ 自增锁 AUTO-INC 锁 了解]]
当一个事务要 插入insert
包含自增列的新记录时,它首先会获取自增锁
- 元数据锁 (
MDL 锁
) [[…/…/…/IT/Mysql/Mysql高级#元数据锁 MDL 锁|Mysql/Mysql高级 > 元数据锁 MDL 锁]]
[[…/…/…/IT/Mysql/Mysql高级#元数据锁 MDL 锁|Mysql/Mysql高级 > 元数据锁 MDL 锁]]
MDL读锁
: 对表做增删改查
操作的时添加,此时不能进行结构变更
MDL写锁
: 对表做结构变更
操作的时添加,此时不能进行增删改查
记录锁 : 可实现两种隔离级别 : 读未提交<读已提交
加S锁
select ... FOR SHARE
一般加X锁,因为读的时候为了脏读,不可重复读,幻读就直接加的X锁
加X锁
select ... FOR UPDATE
8.0 : 添加了`NOWAIT,SKIP LOCKED`语法,能够立刻返回,如果查询的行已经加锁,则会
NOWAIT 立刻报错返回
SKIP LOCKED 立刻返回不包含锁定的行
新插入的数据由隐式锁保护,确保不会被其他事物看到
行锁
如果想要读操作获取写锁就要手动获取
记录锁(Record Locks
)
记录锁就是行锁 (行的读写锁)
[[…/…/…/IT/Mysql/Mysql高级#2 写操作|Mysql/Mysql高级 > 2 写操作]]
间隙锁(Gap Locks)< >
: 解决幻读
间隙锁/临键锁
让 可重复读
隔离级别在一定程度上避免幻读
mysql 8.0
默认 gap-lock
不是关闭
会在插入的记录 主键值
和 mysql 表中上一条记录的主键值之间加入 间隙锁
, 为 开区间
, 闭区间锁
就是 临隙锁
临键锁<= >=
(Next-Key Locks
) 闭空间版本
就是为了给不存在的数据上锁
间隙锁的闭空间版本是在间隙锁的基础上额外锁住了某条数据本身。间隙锁的目的是为了防止其他事务在两个已存在的值之间插入新的行,但是不阻止对已存在行的修改操作。
临键锁,也就是闭空间版本的间隙锁除了锁住间隙,还锁住了间隙内的已存在的数据行,从而提供更严格的数据保护
无法使用索引会导致行锁中本来只在更新语句出现的 记录锁
的基础上再添加 间隙锁
,导致其他事物不能更新间隙中的数据
锁住(7,+∞)
是为了防止事物 A 在查询id<7
后,还可能查询大于 7 的,如果不锁住可能出现幻读
and id = 7 for update;
无索引会锁住整张表
插入意向锁
意向锁是表锁
插入意向锁是行锁
表明有事物想在间隙插入数据,但是现在等待,等待时生成的锁
一个事物在插入一条记录时需要判断该位置是不是被别的事物加了 gap、next-key locks
锁, 如果有则等待,InnoDB
规定在等待时也要在内存中生成一个锁接口,表明有事物想在间隙插入数据,但是现在等待,需要加入一个 插入意向锁
,一种 Gap 锁
(不同意向锁)
第一种情况采用了 MVCC
解决
如果一个事务持有插入意向锁,其他事务在尝试插入到同一间隙时会被阻止
页锁
一个表的数据被组织成一个或多个页, 页锁
是介于 表锁
和 行锁
之间的一种锁粒度
实际上 InnoDB 的行锁是在数据页中标记锁信息,所以可以视为是页锁的更精细控制。
锁定资源比行锁多,比表锁小
Mysql 怎么加行级锁的,锁退化
加锁的对象是索引
加锁基本单位是 next-key lock
(左开右闭
加锁基本范围是相邻索引的范围, 索引顺序 和 当前记录的前一条记录
加锁基本顺序: 索引扫描顺序, 索引的起始位置开始扫描
锁退化条件:唯一索引
next-key lock
的加锁对象必须是 当前查询范围内 的记录。
因为 InnoDB 加锁的机制是以记录和间隙为单位的
唯一索引等值
查询
会锁定主键索引,如果为精准匹配录的索引中的 next-key lock
会退化成记录锁 (原因就是在唯一索引等值查询并且查询记录存在的场景下,仅靠记录锁也能避免幻读的问题)。
(特殊: 如果精准匹配的内容不存在,会退化为间隙锁
,锁定表中前后存在数据所在的区间)
唯一索引 范围
查询
如果声明范围的数不存在于表中,该记录中索引的 next-key临键锁
会退化成 间隙锁
其他扫描的记录,则是在这些记录的索引上加 next-key锁
案例
- 对于
> <
select * from user where id > 15 for update;
会对 >15
的数的区间全部每个都加上 临键锁
, 最后相当于 (15,+∞]
select * from user where id < 5 for update;
select * from user where id < 6 for update;
InnoDB
会从索引的起始
位置开始扫描,直到找到不符合 id < 6
条件的记录
找到 1,5,和第一个不符合的 10
开始加锁
2 个 next-key lock
:分别锁定 (-∞, 1] 和 (1, 5]
1 个间隙锁: 锁定 (5, 10) 范围的间隙。注意这里只有间隙锁,没有行锁,因为 id=10 不满足 id < 6 的条件。
1 和 5 都是查询范围内的记录,要加 next-key lock
- 对于
>= <=
select * from user where id >= 15 for update;
等值匹配的记录本身加上 记录锁
, 最后相当于 [15,+∞]
select * from user where id <= 5 for update;
在针对「小于或者小于等于」的唯一索引(主键索引)范围查询时,存在这两种情况会将索引的 next-key锁
会退化成间隙锁的:
- 二级索引:如果查询是基于二级索引(非聚簇索引)进行的,会首先锁定二级索引,然后再锁定对应的主键索引。为
临键锁
事务 A
: update name='xiaolin' where id =1
事务 B
: update name='xiaolin' where id =5
如果 id
有唯一索引,行锁,事务 A, B 不影响
假设ID 是二级索引,主键是 ID2,则会加上 next-key
锁,事务 A, B 阻塞影响
- 会在 id 索引上,对 id = 1 这条记录加上临键锁,锁定 (-∞, 1] 这个范围(包括记录和间隙)。通过回表,找到 id = 1 对应的 ID2 = 100(假设),会在主键索引上,对 ID2 = 100 这条记录加上临键锁,锁定 (-∞, 100] (这里假设主键值是连续的,实际情况更复杂)
没有唯一索引就会全表扫描,于是就会对所有记录加上 next-key
锁 (记录锁+间隙锁),相当于把整个表锁住了。
那 update 语句的 where 带上索引就能避免全表记录加锁了吗?
并不是。关键还得看这条语句在执行过程种,优化器最终选择的是索引扫描
,还是全表扫描
,如果走了全表扫描,就会对全表的记录加锁了。
- 锁升级
innoDB
引擎不存在锁升级的问题,因为其不是根据每个记录来产生行锁的,是根据每个事务访问的每个页对锁进行管理的。
悲观锁
乐观锁、悲观锁
是一种 设计
思想,上面的分类是 基于实操
临间锁,行锁都可以划分为悲观锁
乐观锁
Mysql
默认悲观锁机制, 但支持乐观锁机制
不采用数据库自身的锁机制,而是通过程序 版本号 T机制
/ CAS 机制
来实现,不存在死锁
java.util.concurrent.atomic
包下的原子变量类就是使用乐观锁的一种实现方式 : CAS 实现
只要有修改的语句就会版本加一 UPDATE ... SET version=version+1 WHERE version=version
每次修改前检查版本是不是和自己读到的版本一致,不一致就说明有别的事物进行了修改,保证了读到的数据是别人没有改过的
版本号机制 /时间戳机制
[[…/…/…/IT/Mysql/Mysql高级#^p2oa6s|Mysql/Mysql高级 > ^p2oa6s]]
MySQL 8.0综合使用MVCC
、锁机制以及版本号机制或CAS机制
MVCC
MVCC (Multiversion Concurrency Control)
,即多版本并发控制技术,由于频繁加解锁会非常影响性能,为了避免只依靠锁,降低锁的使用,通过 MVCC
机制可以 减少对行级锁
的使用, 它使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控制,取而代之的是把数据库的行锁与行的多个版本结合起来,只需要很小的开销, 就可以实现 非锁定读
,从而大大提高数据库系统的并发性能。
如读取数据不会上读锁,在某些特定的查询中,例如涉及到某些聚合函数或子查询,MySQL 可能会选择加锁而不是使用快照。
默认隔离级别下使用 MVCC+锁
依然存在的问题
快照读
: 不加锁 , 有可能读到历史数据,如果串行隔离级别快照读会变成当前读 , 快照读通过 MVCC 提供数据的快照,不会加锁,允许其他事务同时对数据进行修改。
问题: 不一致 (有可能读到历史数据)/幻读
当前读
: 加锁,为了最新数据
MVCC
基于 undo log
和 Read View
undo log链
:关联每个事务的提交,相当于有了数据的各个阶段的各种版本
如何选择需要的版本,根据 readview
读视图
Read view
记录了版本链的统计值,后续查询处理就无需遍历所有的版本链了
creator_trx_id
:创建该Read View
的事务ID
。min_trx_id
:当前数据库中最小活跃事务的ID
。max_trx_id
:当前数据库中最大的事务ID
。m_ids
:活跃事务的ID
集合。
每个事务都会创建自己的 Read view
,包含当前事务自己的
creator_trx_id
, 遍历 undo log链
如果在 undo log链
中
事务 id<min_trx_id
, 则该事务生成前,该事务就已经提交,
事务 id>max_trx_id
, 则是该事务生成 Readview
后别的事务的操作,因此不可见。
事务 min_trx_id<id<max_trx_id & id not in m_ids(活跃事务id)
, 说明该事务已经提交,可以读取。
事务 id=creator_trx_id
是自身事务创建的,可以直接访问
因此,Read View
的作用是帮助事务确定在其创建时哪些数据是可见的,并通过遍历 undo log
来实现这一点。
(设计思想类似:页目录、意向锁)
[[…/…/…/IT/Mysql/Mysql内部结构#隐藏列|Mysql/Mysql高级 > 隐藏列]]
DB_TRX_ID
,事务ID
,每次修改时,都会把该事务 ID
复制给 DB_TRX_ID
;
DB_ROLL_PTR
,回滚指针,指向回滚段的 undo
⽇志
MVCC 可以解决不可重复读
和幻读
吗?
幻读
不可以
不可重复读
可以
读已提交
每次读都生成一个 Readview
可重复读
只生成一个 Readview
, 保证不会读到其他事物的
运维
备份
- 先备份了用户表的数据:
2.然后有用户发起了购买商品的操作:
3.接着再备份商品表的数据。
也就是在备份用户表和商品表之间,有用户购买了商品。
这种情况下
常规备份的结果是用户表中该用户的余额并没有扣除,反而商品表中该商品的库存被减少了, 后面用这个备份文件恢复数据库数据的话,用户钱没少,而库存少了,等于用户白嫖了一件商品。
加 全局锁
: 整个数据库只读,影响业务
解决方式: 使用 mysqldump --single-transaction
,MySQL
在备份数据库之前先开启事务。会创建 readview
, 不影响读和写,这种方式余额和库存都不会减少。这种方法只适用于支持「可重复读隔离级别的事务」的存储引擎。对于 MyISAM 这种不支持事务的引擎,在备份数据库时就要使用全局锁的方法。
其他
![[…/…/…/IT/Mysql/assets/Mysql高级/《面试笔记》——MySQL终结篇(30问与答) (10_12_2023 21_44_09).html|assets/Mysql高级/《面试笔记》——MySQL终结篇(30问与答) (10_12_2023 21_44_09).html]]
![[…/…/…/IT/Mysql/assets/Mysql高级/为什么程序员对这8种SQL的写法零容忍 (09_12_2023 16_30_06) 1.html|assets/Mysql高级/为什么程序员对这8种SQL的写法零容忍 (09_12_2023 16_30_06) 1.html]]