MySQL进阶 —— 超详细操作演示!!!(下)
数据库系列文章:
关系型数据库 — MySQL:
- 1、基础语法大全(上)—— MySQL概述、SQL
- 2、基础语法大全(中)—— 函数、约束
- 3、基础语法大全(下)—— 多表查询
- 4、基础语法大全(下) ——事务
- 5、MySQL进阶(上)—— 存储引擎、 索引
- 6、MySQL进阶(中)—— SQL 优化、视图/存储过程/触发器
- 7、MySQL进阶(下)—— 锁、InnoDB 引擎、MySQL 管理
非关系型数据库 — Redis:
五、锁
5.1 概述
锁是计算机协调多个进程或线程 并发访问 某一资源的机制。
在数据库中,除传统的计算资源(CPU
、RAM
、I/O
)的争用以外,数据
也是一种供许多用户共享的资源。
如何保证数据 并发访问 的一致性
、有效性
是所有数据库必须解决的一个问题,锁冲突 也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。
MySQL中的锁,按照锁的粒度分,分为以下三类:
- 全局锁:锁定数据库中的
所有表
。 - 表级锁:每次操作锁住
整张表
。 - 行级锁:每次操作锁住对应的
行数据
。
5.2 全局锁
⭐️ 1). 介绍
全局锁 就是对 整个数据库实例 加锁,加锁后整个实例就处于 只读状态
,后续的 DML的写语句
,DDL
语句,以及 更新操作
的事务提交语句都将 被阻塞。
其典型的使用场景是做全库的 逻辑备份,对所有的表进行锁定,从而获取 一致性视图
,保证 数据的完整性
。
为什么全库逻辑备份,就需要加全局锁呢?
A. 我们一起先来分析一下不加全局锁,可能存在的问题。
- 假设在数据库中存在这样三张表:
tb_stock
库存表,tb_order
订单表,tb_orderlog
订单日志表。
- 在进行数据备份时,先备份了
tb_stock
库存表。 - 然后接下来,在业务系统中,执行了 下单操作,扣减库存,生成订单(更新
tb_stock
表,插入tb_order
表)。 - 然后再执行备份
tb_order
表的逻辑。 - 业务中执行 插入订单日志 操作。
- 最后,又备份了
tb_orderlog
表。
此时备份出来的数据,是存在问题的。因为备份出来的数据,tb_stock
表与 tb_order
表的数据不一致(有最新操作的订单信息,但是库存数没减)。
那如何来规避这种问题呢?
- 此时就可以借助于MySQL的 全局锁 来解决。
B. 再来分析一下加了全局锁后的情况
对数据库进行进行 逻辑备份 之前,先对整个数据库加上 全局锁 ,一旦加了全局锁之后,其他的 DDL
、
DML
全部都处于 阻塞状态,但是可以执行 DQL
语句,也就是处于 只读状态
,而数据备份 就是 查询操作。
那么数据在进行逻辑备份的过程中,数据库中的数据就是不会发生变化的,这样就保证了数据的 一致性 和 完整性。
⭐️ 2). 语法
a). 加全局锁
flush tables with read lock ;
b). 数据备份
## 不是SQL语句,要在win的命令行中执行!
mysqldump -h 192.168.200.2 -uroot –p1234 rmzh > D:/rmzh.sql
- 数据备份的相关指令, 在后面MySQL管理章节, 还会详细讲解.
c). 释放锁
unlock tables ;
⭐️ 3). 特点
数据库中加全局锁,是一个比较重的操作,存在以下问题:
- 如果在 主库 上备份,那么在备份期间都不能执行更新,业务基本上就得停摆。
- 如果在 从库 上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(
binlog
),会导致主从延迟。
在 InnoDB引擎 中,我们可以在备份时加上参数 --single-transaction
参数来完成不加锁的 一致性
数据备份。
## 不是SQL语句,要在win的命令行中执行!
mysqldump --single-transaction -uroot –p1234 rmzh > rmzh.sql
5.3 表级锁
⭐️ 1). 介绍
表级锁,每次操作 锁住整张表。
- 锁定粒度大,发生锁冲突的概率最高,并发度最低。
- 应用在
MyISAM
、InnoDB
、BDB
等存储引擎中。
对于表级锁,主要分为以下三类:
- 表锁
- 元数据锁(
meta data lock
,MDL
) - 意向锁
⭐️ 2). 表锁
对于表锁,分为两类:
- 表共享读锁(
read lock
) - 表独占写锁(
write lock
)
语法:
- 加锁:
lock tables 表名... read/write
。 - 释放锁:
unlock tables / (客户端断开连接)
。
特点:
A. 读锁
- 左侧为客户端一,对指定表加了读锁,不会影响右侧客户端二的读,但是会 阻塞右侧客户端的写。
测试:
B. 写锁
- 左侧为客户端一,对指定表加了写锁,会 阻塞右侧客户端的读和写。
测试:
结论:
- 读锁 不会阻塞其他客户端的读,但是会阻塞写。
- 写锁 既会阻塞其他客户端的读,又会阻塞其他客户端的写。
⭐️ 3). 元数据锁
meta data lock
, 元数据锁,简写 MDL
。
- MDL加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。
- MDL锁主要作用是维护表元数据的数据 一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。
- 为了避免 DML与DDL冲突,保证读写的正确性。
这里的 元数据,大家可以简单理解为就是 一张表的表结构。 也就是说,某一张表涉及到 未提交的事务 时,是不能够修改这张表的表结构的。
在MySQL5.5中引入了MDL,当对一张表进行增删改查的时候,加 MDL读锁(共享
);当对表结构进行变更操作的时候,加 MDL写锁 (排他
)。
常见的SQL操作时,所添加的元数据锁:
演示:
- 当执行
SELECT
、INSERT
、UPDATE
、DELETE
等语句时,添加的是元数据共享锁(SHARED_READ
/SHARED_WRITE
),之间是 兼容 的。
- 当执行
SELECT
语句时,添加的是 元数据共享锁(SHARED_READ
),会阻塞元数据排他锁(EXCLUSIVE
),之间是 互斥 的。
我们可以通过下面的SQL,来查看数据库中的 元数据锁 的情况:
select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks ;
我们在操作过程中,可以通过上述的SQL语句,来查看元数据锁的加锁情况。
⭐️ 4). 意向锁
a). 介绍
- 为了避免DML在执行时,加的 行锁 与 表锁 的冲突,在InnoDB中引入了 意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查。
假如没有意向锁,客户端一对表加了行锁后,客户端二如何给表加表锁呢,来通过示意图简单分析一下:
- 首先客户端一,开启一个事务,然后执行DML操作,在执行DML语句时,会对涉及到的行加行锁。
- 当客户端二,想对这张表加表锁时,会检查当前表是否有对应的行锁,如果没有,则添加表锁,此时就会从第一行数据,检查到最后一行数据,效率较低。
有了意向锁之后 :
- 客户端一,在执行DML操作时,会对涉及的行加行锁,同时也会对该表加上意向锁。
- 而其他客户端,在对这张表加表锁的时候,会根据该表上所加的意向锁来判定是否可以成功加表锁,而不用逐行判断行锁情况了。
b). 分类
- 意向共享锁(
IS
): 由语句select ... lock in share mode
添加 。 与 表锁共享锁(read
)兼容,与表锁排他锁(write
)互斥。 - 意向排他锁(
IX
): 由insert
、update
、delete
、select...for update
添加 。与表锁共享锁(read
)及 排他锁(write
)都互斥,意向锁之间不会互斥。
一旦事务提交了,意向共享锁、意向排他锁,都会自动释放。
可以通过以下SQL,查看意向锁 及 行锁 的加锁情况:
select object_schema, object_name, index_name, lock_type, lock_mode, lock_data from performance_schema.data_locks;
演示:
-
A. 意向共享锁 与 表读锁 是 兼容 的
-
B. 意向排他锁 与 表读锁、写锁 都是 互斥 的
5.4 行级锁
⭐️ 1). 介绍
行级锁,每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在 InnoDB
存储引擎中(MyISAM
不支持)。
InnoDB的数据是基于索引组织的,行锁是通过对 索引 (聚集索引) 上的索引项加锁来实现的,而不是对记录加的锁。对于行级锁,主要分为以下三类:
- 行锁(
Record Lock
):锁定单个行记录的锁,防止其他事务对此行进行update
和delete
。在RC、RR
隔离级别下都支持。
- 间隙锁(
Gap Lock
):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert
,产生幻读。在RR
隔离级别下都支持。
- 临键锁(
Next-Key Lock
):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的 间隙Gap
。在RR
隔离级别下支持。
⭐️ 2). 行锁
a). 介绍
InnoDB实现了以下两种类型的行锁:
- 共享锁(
S
):允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁。 - 排他锁(
X
):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。
两种行锁的兼容情况如下:
常见的SQL语句,在执行时,所加的行锁如下:
b). 演示
默认情况下,InnoDB
在 REPEATABLE READ
事务隔离级别运行,InnoDB
使用 next-key
锁进行搜索和索引扫描,以防止幻读。
- 针对 唯一索引 进行检索时,对已存在的记录进行等值匹配时,将会自动优化为 行锁。
InnoDB
的 行锁 是针对于 索引加的锁,不通过索引条件检索数据,那么InnoDB
将对表中的所有记录加锁,此时 就会升级为 表锁。
可以通过以下SQL,查看意向锁及行锁的加锁情况:
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
示例演示
数据准备:
CREATE TABLE `stu` (
`id` int NOT NULL PRIMARY KEY AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`age` int NOT NULL
) ENGINE = InnoDB CHARACTER SET = utf8mb4;
INSERT INTO `stu` VALUES (1, 'tom', 1);
INSERT INTO `stu` VALUES (3, 'cat', 3);
INSERT INTO `stu` VALUES (8, 'rose', 8);
INSERT INTO `stu` VALUES (11, 'jetty', 11);
INSERT INTO `stu` VALUES (19, 'lily', 19);
INSERT INTO `stu` VALUES (25, 'luci', 25);
演示行锁的时候,我们就通过上面这张表来演示一下。
- A. 普通的
select
语句,执行时,不会加锁。
- B.
select...lock in share mode
,加共享锁,共享锁与共享锁之间兼容。
- 共享锁 与 排他锁 之间互斥。
客户端一获取的是
id
为1
这行的共享锁,客户端二是可以获取id
为3
这行的 排它锁 的,因为不是同一行数据。 而如果客户端二想获取id
为1
这行的 排他锁,会处于==阻塞状态==,因为为共享锁与排他锁之间互斥。
- C. 排它锁 与 排他锁 之间 互斥
当客户端一,执行
update
语句,会为id
为1
的记录加 排他锁; 客户端二,如果也执行update
语句更新id
为1
的数据,也要为id
为1
的数据加排他锁,但是客户端二会处于 阻塞状态,因为排他锁之间是互斥的。 直到客户端一,把事务提交了,才会把这一行的行锁释放,此时客户端二,解除阻塞。
- D. 无索引行锁 升级为表锁
- stu表中数据如下:
- 我们在两个客户端中执行如下操作:
在客户端一中,开启事务,并执行
update
语句,更新name
为Lily
的数据,也就是id
为19
的记录 。然后在客户端二中更新id
为3
的记录,却不能直接执行,会处于阻塞状态,为什么呢?
- 原因就是因为此时,客户端一,根据
name
字段进行更新时,name
字段是没有索引的,如果没有索引,此时行锁会升级为表锁(因为行锁是对索引项加的锁,而name
没有索引)。
- 接下来,我们再针对name字段建立索引,索引建立之后,再次做一个测试:
此时我们可以看到,客户端一,开启事务,然后依然是根据
name
进行更新。而客户端二,在更新id
为3
的数据时,更新成功,并 未进入 阻塞状态。 这样就说明,我们根据索引字段进行更新操作,就可以 避免 行锁 升级为 表锁 的情况。
⭐️ 3). 间歇锁 & 临键锁
默认情况下,InnoDB
在 REPEATABLE READ
事务隔离级别运行,InnoDB
使用 next-key
锁进行搜索和索引扫描,以防止幻读。
- 索引上的等值查询(唯一索引),给不存在的记录加锁时, 优化为间隙锁 。
- 索引上的等值查询(非唯一普通索引),向右遍历时最后一个值不满足查询需求时,
next-key lock
退化为 间隙锁。 - 索引上的范围查询(唯一索引) – 会访问到不满足条件的第一个值为止。
注意:间隙锁 唯一目的是防止其他事务插入间隙。间隙锁 可以共存,一个事务采用的间隙锁不会阻止另一个事务在同一间隙上采用间隙锁。
示例演示
- A. 索引上的等值查询(唯一索引),给不存在的记录加锁时, 优化为间隙锁 。
- B. 索引上的等值查询(
非唯一
普通索引),向右遍历时最后一个值不满足查询需求时,next-key lock
退化为间隙锁。
介绍分析一下:
我们知道InnoDB的B+树索引,叶子节点是有序的双向链表。 假如,我们要根据这个二级索引查询值
为18的数据,并加上共享锁,我们是只锁定18这一行就可以了吗? 并不是,因为是非唯一索引,这个
结构中可能有多个18的存在,所以,在加锁时会继续往后找,找到一个不满足条件的值(当前案例中也
就是29)。此时会对18加临键锁,并对29之前的间隙加锁。
- C. 索引上的范围查询(唯一索引) – 会访问到不满足条件的第一个值为止。
查询的条件为
id>=19
,并添加共享锁。 此时我们可以根据数据库表中现有的数据,将数据分为三个部
分:
[19]
(19,25]
(25,+∞]
所以数据库数据在加锁是,就是将19加了行锁,25的临键锁(包含25及25之前的间隙),正无穷的临键锁(正无穷及之前的间隙)。
🚀🚀🚀 锁 快速食用:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------->
## 1、概述
-- 在并发访问时,解决数据访问的一致性、有效性问题
-- 全局锁、表级锁、行级锁
## 2、全局锁
-- 对整个数据库实例加锁,加锁后整个实例就处于只读状态
-- 性能较差,数据逻辑备份时使用
## 3、表级锁
-- 操作锁住整张表,锁定粒度大,发生锁冲突的概率高
-- 表锁、元数据锁、意向锁
## 4、行级锁
-- 操作锁住对应的行数据,锁定粒度最小,发生锁冲突的概率最低
-- 行锁、间隙锁、临键锁
六、InnoDB 引擎
6.1 逻辑存储结构
InnoDB的 逻辑存储 结构如下图所示:
1). 表空间
- 表空间是
InnoDB
存储引擎 逻辑结构的最高层; - 如果用户启用了参数
innodb_file_per_table
(在8.0版本中默认开启) ,则每张表都会有一个 表空间(xxx.ibd
),一个 mysql 实例可以 对应 多个表空间,用于存储记录、索引等数据。
2). 段
- 段,分为数据段(
Leaf node segment
)、索引段(Non-leaf node segment
)、回滚段(Rollback segment
); InnoDB
是 索引组织表,数据段就是B+树
的 叶子节点, 索引段即为B+树的 非叶子节点。- 段用来管理多个
Extent
(区)。
3). 区
- 区,表空间 的 单元结构,每个区的大小为
1M
。 - 默认情况下,
InnoDB
存储引擎页大小为16K
, 即一个区中一共有64
个 连续的页。
4). 页
- 页,是
InnoDB
存储引擎 磁盘管理 的 最小单元,每个页的大小默认为16KB
。 - 为了保证 页的连续性,
InnoDB
存储引擎每次从磁盘申请 4-5 个区。
5). 行
- 行,
InnoDB
存储引擎 数据是按行进行存放的。 - 在行中,默认有两个隐藏字段:
Trx_id
:每次对某条记录进行改动时,都会把对应的事务id
赋值给trx_id
隐藏列。Roll_pointer
:每次对某条引记录进行改动时,都会把旧的版本写入到undo
日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。
6.2 架构
⭐️ 1). 概述
- MySQL5.5 版本开始,默认使用
InnoDB
存储引擎,它擅长事务处理,具有崩溃恢复特性,在日常开发中使用非常广泛。
下面是 InnoDB
架构图,左侧为内存结构,右侧为磁盘结构。
⭐️ 2). 内存结构
- 在左侧的内存结构中,主要分为这么四大块儿:
Buffer Pool
(缓冲池)、Change Buffer
、Adaptive Hash Index
、Log Buffer
。 接下来介绍一下这四个部分。
a). Buffer Pool
InnoDB
存储引擎基于 磁盘文件 存储,访问 物理硬盘 和在 内存 中进行访问,速度相差很大,为了尽可能弥补这两者之间的I/O效率的差值,就需要把经常使用的数据加载到 缓冲池 中,避免每次访问都进行磁盘I/O。- 在
InnoDB
的缓冲池中不仅缓存了索引页
和数据页
,还包含了undo页
、插入缓存
、自适应哈希索引
以及InnoDB的锁信息
等等。
缓冲池 Buffer Pool
,是主内存中的一个区域,里面可以缓存磁盘上经常操作的真实数据,在执行增删改查操作时,先操作缓冲池中的数据(若缓冲池没有数据,则从磁盘加载并缓存),然后再以一定频率刷新到磁盘,从而减少磁盘IO,加快处理速度。
缓冲池以 Page
页为单位,底层采用 链表数据结构管理 Page
。根据状态,将Page
分为三种类型:
free page
:空闲page
,未被使用。clean page
:被使用page
,数据没有被修改过。dirty page
:脏页,被使用page
,数据被修改过,也中数据与磁盘的数据产生了不一致。
在专用服务器上,通常将多达80%的物理内存分配给缓冲池 。参数设置: show variables like 'innodb_buffer_pool_size';
b). Change Buffer
Change Buffer
,更改缓冲区(针对于 *非唯一 二级索引页 ),在执行DML
语句时,如果这些数据Page
没有在Buffer Pool
中,不会直接操作磁盘,而会将数据变更存在 更改缓冲区Change Buffer
中,在未来数据被读取时,再将数据合并恢复到Buffer Pool
中,再将合并后的数据刷新到磁盘中。
Change Buffer 的意义是什么呢?
- 先来看一幅图,这个是二级索引的结构图:
与聚集索引不同,二级索引通常是非唯一的,并且以相对随机的顺序插入二级索引。
同样,删除和更新可能会影响索引树中不相邻的二级索引页,如果每一次都操作磁盘,会造成大量的磁盘IO。有了Change Buffer
之后,我们可以在缓冲池中进行合并处理,减少磁盘IO。
c). Adaptive Hash Index
- 自适应 hash 索引,用于优化对
Buffer Pool
数据的查询。 - MySQL的
innoDB
引擎中虽然没有直接支持hash索引,但是给我们提供了一个功能就是这个自适应hash索引。因为前面我们讲到过,hash索引在进行等值匹配时,一般性能是要高于B+树
的,因为hash索引
一般只需要一次IO即可,而B+树
,可能需要几次匹配,所以hash索引的效率要高,但是hash索引又不适合做范围查询、模糊匹配等。 - InnoDB存储引擎会监控对表上各索引页的查询,如果观察到在特定的条件下hash索引可以提升速度,则建立hash索引,称之为自适应hash索引。
自适应哈希索引,无需人工干预,是系统根据情况自动完成。
- 参数:
adaptive_hash_index
d). Log Buffer
- Log Buffer:日志缓冲区,用来保存要写入到磁盘中的
log
日志数据(redo log
、undo log
),默认大小为16MB
,日志缓冲区的日志会定期刷新到磁盘中。 - 如果需要更新、插入或删除许多行的事务,增加日志缓冲区的大小可以节省磁盘 I/O。
参数:
innodb_log_buffer_size
:缓冲区大小innodb_flush_log_at_trx_commit
:日志刷新到磁盘时机,取值主要包含以下三个:1
: 日志在每次事务提交时写入并刷新到磁盘,默认值。0
: 每秒将日志写入并刷新到磁盘一次。2
: 日志在每次事务提交后写入,并每秒刷新到磁盘一次。
⭐️ 3). 磁盘结构
- 接下来,再来看看InnoDB体系结构的右边部分,也就是磁盘结构:
a). System Tablespace
-
系统表空间 是 更改缓冲区的存储区域。
-
如果表是在 系统表空间 而不是每个表文件或通用表空间中创建的,它也可能包含表和索引数据。(在MySQL5.x版本中还包含 InnoDB数据字典、
undolog
等) -
参数:
innodb_data_file_path
-
系统表空间,默认的文件名叫
ibdata1
。
b). File-Per-Table Tablespaces
-
如果开启了
innodb_file_per_table
开关 ,则每个表的 文件表空间 包含单个InnoDB
表的数据和索引 ,并存储在文件系统上的单个数据文件中。 -
开关参数:
innodb_file_per_table
,该参数默认开启。 -
那也就是说,我们每创建一个表,都会产生一个表空间文件,如图:
c). General Tablespaces
- 通用表空间,需要通过
CREATE TABLESPACE
语法创建通用表空间,在创建表时,可以指定该表空间。 - A. 创建表空间
CREATE TABLESPACE ts_name ADD DATAFILE 'file_name' ENGINE = engine_name;
- B. 创建表时指定表空间
CREATE TABLE xxx ... TABLESPACE ts_name;
d). Undo Tablespaces
- 撤销表空间,MySQL实例在初始化时会自动创建两个默认的
undo
表空间(初始大小16M
),用于存储undo log
日志。
e). Temporary Tablespaces
InnoDB
使用 会话临时表空间 和 全局临时表空间。存储用户创建的临时表等数据。
f). Doublewrite Buffer Files
- 双写缓冲区,
innoDB
引擎将数据页从Buffer Poo
l刷新到磁盘前,先将数据页写入双写缓冲区文件中,便于系统异常时恢复数据。
g). Redo Log
- 重做日志,是用来实现事务的持久性。
- 该日志文件由两部分组成:重做日志缓冲(
redo log buffer
)以及重做日志文件(redo log
),前者是在内存中,后者在磁盘中。 - 当事务提交之后会把所有修改信息都会存到该日志中, 用于在刷新脏页到磁盘时,发生错误时, 进行数据恢复使用。
- 以循环方式写入重做日志文件,涉及两个文件:
前面我们介绍了InnoDB的内存结构,以及磁盘结构,那么内存中我们所更新的数据,又是如何到磁盘中的呢?
- 此时,就涉及到一组 后台线程,接下来,就来介绍一些InnoDB中涉及到的 后台线程。
⭐️ 4). 后台线程
在InnoDB
的后台线程中,分为4类,分别是:Master Thread
、IO Thread
、Purge Thread
、Page Cleaner Thread
。
a). Master Thread
- 核心后台线程,负责调度其他线程,还负责将缓冲池中的数据异步刷新到磁盘中, 保持数据的一致性,还包括脏页的刷新、合并插入缓存、
undo
页的回收 。
b). IO Thread
- 在
InnoDB
存储引擎中大量使用了AIO
(异步非阻塞IO
) 来处理IO请求, 这样可以极大地提高数据库的性能,而IOThread
主要负责这些 IO请求的回调。
- 我们可以通过以下的这条指令,查看到
InnoDB
的状态信息,其中就包含IO Thread
信息。
show engine innodb status \G;
c). Purge Thread
- 主要用于回收 事务已经提交了的
undo log
,在事务提交之后,undo log
可能不用了,就用它来回收。
d). Page Cleaner Thread
- 协助
Master Thread
刷新脏页到磁盘的线程,它可以减轻Master Thread
的工作压力,减少阻塞。
6.3 事务原理
⭐️ 1). 事务基础
a). 事务
- 事务 是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统 提交 或 撤销操作请求,即这些操作 要么同时成功,要么同时失败。
b). 特性
- 原子性(
Atomicity
):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。 - 一致性(
Consistency
):事务完成时,必须使所有的数据都保持一致状态。 - 隔离性(
Isolation
):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。 - 持久性(
Durability
):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
那实际上,我们研究事务的原理,就是研究MySQL的InnoDB引擎是如何保证事务的这四大特性的。
而对于这四大特性,实际上分为两个部分。
- 其中的 原子性、一致性、持久化,实际上是由InnoDB中的两份日志来保证的,一份是
redo log
日志,一份是undo log
日志。 - 而 隔离性 是通过数据库的锁,加上
MVCC
来保证的。
我们在讲解事务原理的时候,主要就是来研究一下 redolog
,undolog
以及 MVCC
。
⭐️ 2). redo log
- 重做日志,记录的是事务提交时 数据页的物理修改,是用来实现事务的 持久性。
- 该日志文件由两部分组成:重做日志缓冲(
redo log buffer
)以及重做日志文件(redo log file
),前者是在 内存 中,后者在 磁盘 中。当事务提交之后会把所有修改信息都存到该日志文件中, 用于在刷新脏页到磁盘,发生错误时, 进行数据恢复使用。
如果没有 redo log
,可能会存在什么问题的? 我们一起来分析一下。
我们知道,在InnoDB引擎中的内存结构中,主要的内存区域 就是 缓冲池,在缓冲池中缓存了很多的数据页。
- 当我们在一个事务中,执行多个增删改的操作时,InnoDB引擎会先操作缓冲池中的数据,如果 缓冲区 没有对应的数据,会通过 后台线程 将磁盘中的数据加载出来,存放在 缓冲区 中,然后将缓冲池中的数据修改,修改后的数据页我们称为 脏页。
- 而 脏页 则会在一定的时机,通过后台线程刷新到磁盘中,从而保证缓冲区与磁盘的数据一致。 而缓冲区的脏页数据并不是实时刷新的,而是一段时间之后将缓冲区的数据刷新到磁盘中,假如刷新到磁盘的过程出错了,而提示给用户事务提交成功,而数据却没有持久化下来,这就出现问题了,没有保证事务的持久性。
那么,如何解决上述的问题呢? 在InnoDB中提供了一份日志 redo log
,接下来我们再来分析一下,通过 redolog
如何解决这个问题。
有了
redolog
之后,当对缓冲区的数据进行增删改之后,会首先将操作的 数据页的变化,记录在redo log buffer
中。在事务提交时,会将redo log buffer
中的数据刷新到redo log
磁盘文件中。
过一段时间之后,如果 刷新缓冲区 的 脏页 到磁盘时,发生错误,此时就可以借助于redo log
进行数据恢复,这样就 保证了事务的持久性。
而如果脏页成功刷新到磁盘 或 涉及到的数据已经落盘,此时redo log
就没有作用了,就可以删除了,所以存在的两个redo log
文件是循环写的。
那为什么每一次提交事务,要刷新
redo log
到磁盘中呢,而不是直接将buffer pool
中的脏页刷新到磁盘呢 ?
- 因为在业务操作中,我们操作数据一般都是随机读写磁盘的,而不是顺序读写磁盘。 而
redo log
在往磁盘文件中写入数据,由于是日志文件,所以都是顺序写的。顺序写的效率,要远大于随机写。- 这种 先写日志 的方式,称之为
WAL
(Write-Ahead Logging
)。
⭐️ 3). undo log
- 回滚日志,用于记录数据被 修改前的信息 , 作用包含两个 : 提供回滚 (保证事务的 原子性) 和MVCC(多版本并发控制) 。
undo log
和redo log
记录 物理日志 不一样,它是 逻辑日志。可以认为当delete
一条记录时,undo log
中会记录一条对应的insert
记录,反之亦然,当update
一条记录时,它记录一条对应相反的update
记录。当执行rollback
时,就可以从undo log
中的逻辑记录读取到相应的内容并进行回滚。Undo log
销毁:undo log
在事务执行时产生,事务提交时,并不会立即删除undo log
,因为这些日志可能还用于MVCC
。Undo log
存储:undo log
采用 段的方式 进行管理和记录,存放在前面介绍的rollback segment
回滚段 中,内部包含1024个undo log segment
。
6.4 MVCC —— 多版本并发控制
⭐️ 1). 基本概念
a). 当前读
- 读取的是记录的最新版本,读取时还要保证其他并发事务 不能修改当前记录, 会对读取的记录进行加锁。
- 对于我们日常的操作,如:
select ... lock in share mode
(共享锁),select ... for update、update、insert、delete
(排他锁) 都是一种 当前读。
测试:
在测试中我们可以看到,即使是在默认的 RR隔离级别 下,事务A中依然可以读取到事务B最新提交的内容,因为在查询语句后面加上了
lock in share mode
共享锁,此时是当前读操作。当然,当我们加排他锁的时候,也是当前读操作。
b). 快照读
- 简单的
select
(不加锁)就是快照读,读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。 Read Committed
:每次select
,都生成一个快照读。Repeatable Read
:开启事务后第一个select
语句才是快照读的地方。Serializable
:快照读会 退化 为 当前读。
测试:
在测试中,我们看到即使 事务B提交了数据,事务A中也查询不到 。
- 原因就是因为普通的
select
是 快照读,而在当前默认的 RR隔离级别下,开启事务后第一个select
语句才是快照读的地方,后面执行相同的select
语句都是 从快照中获取数据,可能不是当前的最新数据,这样也就 保证了可重复读。
c). MVCC
- 全称
Multi-Version Concurrency Control
,多版本并发控制。 - 指维护一个数据的多个版本,使得读写操作没有冲突,快照读为MySQL实现
MVCC
提供了一个 非阻塞读功能。 - MVCC的具体实现,还需要依赖于数据库记录中的
三个隐式字段
、undo log
日志、readView
。
接下来,我们再来介绍一下InnoDB引擎的表中涉及到的 隐藏字段
、undolog
以及 readview
,从而来介绍一下MVCC的原理。
⭐️ 2). 隐藏字段
a). 介绍
- 当我们创建了上面的这张表,我们在查看表结构的时候,就可以显式的看到这三个字段。 实际上除了这三个字段以外,InnoDB还会自动的给我们添加 三个隐藏字段 及其含义分别是:
而上述的前两个字段是肯定会添加的, 是否添加最后一个字段 DB_ROW_ID
,得看当前表有没有主键,如果有主键,则不会添加该隐藏字段。
b). 测试:
查看有主键的表 stu
- 进入服务器中的
/var/lib/mysql/rmzh/
, 查看stu的表结构信息, 通过如下指令:
ibd2sdi stu.ibd
- 查看到的表结构信息中,有一栏
columns
,在其中我们会看到处理我们建表时指定的字段以外,还有额外的两个字段 分别是:DB_TRX_ID
、DB_ROLL_PTR
,因为该表有主键,所以没有DB_ROW_ID
隐藏字段。
c). 查看没有主键的表 employee
- 建表语句:
create table employee (id int , name varchar(10));
- 此时,我们再通过以下指令来查看表结构及其其中的字段信息:
ibd2sdi employee.ibd
- 查看到的表结构信息中,有一栏
columns
,在其中我们会看到处理我们建表时指定的字段以外,还有额外的三个字段 分别是:DB_TRX_ID
、DB_ROLL_PTR
、DB_ROW_ID
,因为employee
表是没有指定主键的。
⭐️ 3). undolog
a). 介绍
- 回滚日志,在
insert
、update
、delete
的时候产生的便于数据回滚的日志。 - 当
insert
的时候,产生的undo log
日志只在回滚时需要,在事务提交后,可被 立即删除 。 - 而
update
、delete
的时候,产生的undo log
日志不仅在回滚时需要,在快照读时也需要,不会立即被删除。
b). 版本链
有一张表原始数据为:
DB_TRX_ID : 代表最近 修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID,是自增的。
DB_ROLL_PTR : 由于这条数据是才插入的,没有被更新过,所以该字段值为null
。
然后,有四个并发事务同时在访问这张表。
- A. 第一步
当 事务2 执行第一条修改语句时,会记录 undo log
日志,记录数据变更之前的样子; 然后更新记录,且记录本次操作的 事务ID,回滚指针,回滚指针用来指定如果发生回滚,回滚到哪一个版本。
B.第二步
当 事务3 执行第一条修改语句时,也会记录 undo log
日志,记录数据变更之前的样子; 然后更新记录,并且记录本次操作的事务ID,回滚指针,回滚指针用来指定如果发生回滚,回滚到哪一个版本。
C. 第三步
当事务4执行第一条修改语句时,也会记录 undo log
日志,记录数据变更之前的样子; 然后更新记录,并且记录本次操作的事务ID,回滚指针,回滚指针用来指定如果发生回滚,回滚到哪一个版本。
最终我们发现,不同事务 或 相同事务 对同一条记录进行修改,会导致该记录的
undo log
生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。
⭐️ 4). readview
-
ReadView
(读视图)是 快照读 SQL执行时MVCC提取数据的依据,记录并维护系统 当前活跃的事务(未提交的)id
。 -
ReadView
中包含了四个核心字段:
而在 readview
中就规定了 版本链 数据的访问规则:
trx_id
代表当前undolog
版本链 对应 事务ID。
不同的隔离级别,生成 ReadView
的时机不同:
READ COMMITTED
:在事务中 每一次 执行 快照读 时生成ReadView
。REPEATABLE READ
:仅在事务中第一次执行 快照读 时生成ReadView
,后续复用 该ReadView
。
⭐️ 5). 原理分析
a). RC隔离级别
- RC隔离级别下,在事务中每一次执行 快照读 时生成
ReadView
。
我们就来分析事务5中,两次快照读读取数据,是如何获取数据的?
- 在事务5中,查询了两次
id
为30的记录,由于隔离级别为Read Committed
,所以每一次进行 快照读 都会生成一个ReadView
,那么两次生成的ReadView
如下。
- 那么这两次 快照读 在获取数据时,就需要根据所生成的
ReadView
以及ReadView
的版本链访问规则,到undo log
版本链中匹配数据,最终决定此次快照读返回的数据。
A. 先来看第一次快照读具体的读取过程:
- 在进行匹配时,会从
undo log
的版本链,从上到下进行挨个匹配:
B. 再来看第二次快照读具体的读取过程:
在进行匹配时,会从undo log的版本链,从上到下进行挨个匹配:
b). RR隔离级别
- RR隔离级别下,仅在事务中第一次执行快照读时生成
ReadView
,后续复用该ReadView
。 而RR
是可重复读,在一个事务中,执行两次相同的select
语句,查询到的结果是一样的。
那MySQL是如何做到可重复读的呢? 我们简单分析一下就知道了
- 我们看到,在RR隔离级别下,只是在事务中第一次快照读时生成
ReadView
,后续都是复用该ReadView
,那么既然ReadView
都一样,ReadView
的版本链匹配规则也一样, 那么最终快照读返回的结果也是一样的。
所以呢,MVCC
的实现原理就是通过 InnoDB
表的隐藏字段、UndoLog
版本链、ReadView
来实现的。
而MVCC +
锁,则实现了事务的隔离性。 而一致性则是由 redolog
与 undolog
保证。
🚀🚀🚀 InnoDB 引擎 快速食用:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------->
## 1. 逻辑存储结构
表空间、段、区(1M)、页(16K)、行
## 2、架构
内存结构
磁盘结构
## 3、事务原理
原子性 - undo log
持久性 - redo log
一致性 - undo log + redo log
隔离性 - 锁 + MVCC
## 4、MVCC (多版本并发控制)
记录隐藏字段、undo log版本链、readView (读视图)
七、MySQL 管理
7.1 系统数据库
Mysql数据库安装完成后,自带了以下四个数据库,具体作用如下:
7.2 常用工具
⭐️ 1). mysql
- 该
mysql
不是指mysql
服务,而是指mysql
的客户端工具。
语法 :
mysql [options] [database]
选项 :
-u, --user=name #指定用户名
-p, --password[=name] #指定密码
-h, --host=name #指定服务器IP或域名
-P, --port=port #指定连接端口
-e, --execute=name #执行SQL语句并退出
-e
选项可以在Mysql客户端执行SQL语句,而不用连接到MySQL数据库再执行,对于一些批处理脚本,这种方式尤其方便。
示例:
## 不用登录MySQL
mysql -h192.168.200.202 -P3306 -uroot -p1234 rmzh -e "select * from stu";
mysql -uroot -p1234 rmzh -e "select * from stu";
⭐️ 2). mysqladmin
mysqladmin
是一个执行管理操作的客户端程序。可以用它来检查服务器的配置
和当前状态
、创建并删除数据库
等。
通过帮助文档查看选项:
mysqladmin --help
语法:
mysqladmin [options] command ...
选项:
-u, --user=name #指定用户名
-p, --password[=name] #指定密码
-h, --host=name #指定服务器IP或域名
-P, --port=port #指定连接端口
示例:
## 如果不指定主机名及主机端口号,代表查看本机3306 MySQL
mysqladmin -uroot -p1234 drop 'test01';
mysqladmin -uroot -p1234 version;
mysqladmin -uroot -p1234 create db02;
⭐️ 3). mysqlbinlog
- 由于服务器生成的 二进制日志文件 以 二进制格式保存,所以如果想要检查这些文本的文本格式,就会使用到
mysqlbinlog
日志管理工具。
语法 :
mysqlbinlog [options] log-files1 log-files2 ...
选项 :
-d, --database=name 指定数据库名称,只列出指定的数据库相关操作。
-o, --offset=# 忽略掉日志中的前n行命令。
-r,--result-file=name 将输出的文本格式日志输出到指定文件。
-s, --short-form 显示简单格式, 省略掉一些信息。
--start-datatime=date1 --stop-datetime=date2 指定日期间隔内的所有日志。
--start-position=pos1 --stop-position=pos2 指定位置间隔内的所有日志。
示例:
- A. 查看
binlog.000008
这个二进制文件中的数据信息
⭐️ 4). mysqlshow
mysqlshow
客户端对象查找工具,用来很快地 查找 存在哪些 数据库、数据库中的表、表中的列 或者 索引。
语法 :
mysqlshow [options] [db_name [table_name [col_name]]]
选项 :
--count 显示数据库及表的统计信息(数据库,表 均可以不指定)
-i 显示指定数据库或者指定表的状态信息
示例:
#查询test库中每个表中的字段书,及行数
mysqlshow -uroot -p2143 test --count
#查询test库中book表的详细情况
mysqlshow -uroot -p2143 test book --count
示例:
- A. 查询每个数据库的 表的数量及 表中记录的数量
mysqlshow -uroot -p1234 --count
- B. 查看数据库
rmzh
的统计信息
mysqlshow -uroot -p1234 rmzh --count
- C. 查看数据库
rmzh
中的course
表的信息
mysqlshow -uroot -p1234 rmzh course --count
- D. 查看数据库
rmzh
中的course
表的id
字段的信息
mysqlshow -uroot -p1234 rmzh course id --count
⭐️ 5). mysqldump
mysqldump
客户端工具用来 备份数据库 或在 不同数据库之间进行数据迁移。备份内容包含创建表,及插入表的SQL语句。
语法 :
mysqldump [options] db_name [tables]
mysqldump [options] --database/-B db1 [db2 db3...]
mysqldump [options] --all-databases/-A
连接选项 :
-u, --user=name 指定用户名
-p, --password[=name] 指定密码
-h, --host=name 指定服务器ip或域名
-P, --port=# 指定连接端口
输出选项:
--add-drop-database 在每个数据库创建语句前加上 drop database 语句
--add-drop-table 在每个表创建语句前加上 drop table 语句 , 默认开启 ; 不
开启 (--skip-add-drop-table)
-n, --no-create-db 不包含数据库的创建语句
-t, --no-create-info 不包含数据表的创建语句
-d --no-data 不包含数据
-T, --tab=name 自动生成两个文件:一个.sql文件,创建表结构的语句;一个.txt文件,数据文件
示例:
- A. 备份
rmzh
数据库
mysqldump -uroot -p1234 rmzh > rmzh.sql
- 可以直接打开
rmzh.sql
,来查看备份出来的数据到底什么样。
cat rmzh.sql
备份出来的数据包含:
- 删除表的语句
- 创建表的语句
- 数据插入语句
如果我们在数据备份时,不需要创建表,或者不需要备份数据,只需要备份表结构,都可以通过对应的参数来实现。
- B. 备份
rmzh
数据库中的表数据,不备份表结构(-t
)
mysqldump -uroot -p1234 -t rmzh > rmzh01.sql
打开 rmzh01.sql
,来查看备份的数据,只有 insert
语句,没有备份表结构。
- C. 将
rmzh
数据库的表的 表结构 与 数据 分开备份(-T
)
mysqldump -uroot -p1234 -T /root rmzh score
执行上述指令,会出错,数据不能完成备份,原因是因为我们所指定的数据存放目录
/root
,MySQL认为是不安全的,需要存储在MySQL信任的目录下。那么,哪个目录才是MySQL信任的目录呢,可以查看一下系统变量secure_file_priv
。执行结果如下:
上述的两个文件 score.sql
中记录的就是表结构文件,而 score.txt
就是表数据文件,但是需要注意表数据文件,并不是记录一条条的 insert
语句,而是按照一定的格式记录表结构中的数据。如下:
⭐️ 6). mysqlimport/source
- 1). mysqlimport
mysqlimport
是客户端数据导入工具,用来导入mysqldump
加-T
参数后导出的 文本文件。
语法 :
mysqlimport [options] db_name textfile1 [textfile2...]
示例 :
mysqlimport -uroot -p2143 test /tmp/city.txt
- 2). source
- 如果需要导入
sql
文件,可以使用mysql
中的source
指令 :
- 如果需要导入
语法 :
source /root/xxxxx.sql # 在mysql 的命令行中执行
🚀🚀🚀 MySQL 管理 快速食用:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------->
## 1、mysql
Mysql 客户端工具,-e 执行SQL并退出 # 通常在一些脚本编写的时候会用到这个工具
## 2、mysqladmin
Mysql 管理工具
## 3、mysqlbinlog
二进制日志查看工具
## 4、mysqlshow
查看数据库、表、字段的统计信息
## 5、mysqldump
数据备份工具
## 6、mysqlimport/source
数据导入
注:仅供学习参考,如有不足,欢迎指正!!!