MySQL 学习笔记(进阶篇3)

一、锁

1. 介绍

锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、RAM、I/0)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。

2. 分类

MySQL中的锁,按照锁的粒度分,分为一下三类:

  • 全局锁:锁定数据库中的所有表。
  • 表级锁:每次操作锁住整张表。
  • 行级锁:每次操作锁住对应的行数据。

二、全局锁

1. 介绍

① 全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,已经更新操作的事务提交语句都将被阻塞。

② 其典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。

2. 演示图
3. 基本操作

① 使用全局锁:

sql 复制代码
flush tables with read lock

② 释放全局锁:

sql 复制代码
unlock tables
4. 特点:

数据库中加全局锁,是一个比较重的操作,存在以下问题:

  • 如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆
  • 如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导致主从延迟。(该结构会在后续主从复制讲解)
5. 解决方法:

在InnoDB引擎中,我们可以在备份时加上参数 --single-transaction 参数来完成不加锁的一致性数据备份。

bash 复制代码
mysqldump --single-transaction -uroot -p123456 itcast > itcast.sql

注意:

这个方法只对 InnoDB 引擎有用(因为只有 InnoDB 支持事务);如果是 MyISAM 引擎,备份还是会锁表(MyISAM 不支持事务,没法搞 "快照")。

三、表级锁
1. 介绍

每次操作锁住整张表。锁定粒度大,发生锁的冲突的概率最高,并发度最低。应用在MyISAM、InnoDB、BDB等存储引擎中。对于表级锁,主要分为一下三类:

  • 表锁
  • 元数据锁(meta data lock,MDL)
  • 意向锁
2. 表锁

① 对于表锁,分为两类:

  • 表共享读锁(read lock)
  • 表独占写锁(write lock)

读锁不会阻塞其他客户端的读,但是会阻塞写。写锁既会阻塞其他客户端的读,又会阻塞其他客户端的写。

② 语法:

bash 复制代码
#表级别的共享锁,也就是读锁;
#允许当前会话读取被锁定的表,但阻止当前会话和其他会话对这些表进行写操作。
lock tebles t_student read;

#表级锁的独占锁,也是写锁;
#允许当前会话对表进行读写操作,但阻止其他会话对这些表进行任何操作(读或写)。
lock tables t_stuent write;

#会话退出,也会释放所有锁
unlock tables
3. 元数据锁

① MDL加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。MDL锁主要作用是维护表元数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。为了避免DML与DDL冲突,保证读写的正确性。

  • 对一张表进行 CRUD(增删改查)操作时,加的是 MDL 读锁
  • 对一张表做结构变更操作的时候,加的是 MDL 写锁
对应SQL 锁类型 说明
lock tables xxx read /write SHARED_READ_ONLY/SHARED_NO_READ_WRITE
select 、 select ... lock in share mode SHARED_READ 与SHARED_READ、SHARED_WRITE兼容,与EXCLUSIVE互斥
insert 、update、delete、select ...for update SHARED_WRITE 与SHARED_READ、SHARED_WRITE兼容,与EXCLUSIVE互斥
alter table ... EXCLYSIVE 与其他的MDL都互斥

② 查看元数据锁

sql 复制代码
select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks;
4. 意向锁

(1) 为什么需要意向锁?

若没有意向锁,加表锁 时需要逐行检查表中所有记录是否有行锁(比如表有 10 万行,需检查 10 万次),效率极低。

(2) 核心类比

把数据库表比作「教室」,表中每一行比作「座位」:

  • 行锁 = 占某个座位;
  • 意向锁 = 教室门口贴的纸条(提示 "教室内已有座位被占用");
  • 表锁 = 锁整个教室(要求教室内无任何占座)。

(3) 意向共享锁和意向独占锁是表级锁,不会和行级的共享锁和独占锁发生冲突,而且意向锁之间也不会发生冲突,只会和**共享表锁(lock tables ... read)和独占表锁(lock tables ... write)**发生冲突

① 意向共享锁(IS):与表锁共享锁(read)兼容,与表锁排它锁(write)互斥。

② 意向排他锁(IX):与表锁共享锁(read)及排它锁(write)都互斥。

(4) 加锁方式:

sql 复制代码
-- 意向共享锁:(先在表上加上意向共享锁,然后对读取的记录加共享锁)
select ... lock in share mode

-- 意向独占锁:(先表上加上意向独占锁,然后对读取的记录加独占锁)
insert、update、delete、select ... for update

-- 可以通过以下SQL,查看意向锁及行锁的枷锁情况
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;

四、行级锁

行级锁,每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在InnoDB存储引擎中。

  • 行锁(Record Lock):锁定单个行记录的锁,防止其他事务对此行进行update和delete。在RC、RR隔离级别下都支持。
  • 间隙锁(GapLock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在RR隔离级别下都支持。
  • 临键锁(Next-Key Lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap。在RR隔离级别下支持。
1. Record Lock(行锁)

Record Lock 称为记录锁,锁住的是一条记录。而且记录锁是有 S 锁和 X 锁之分。

(1) InnoDB实现了以下两种类型的行锁:

  • 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁。
  • 排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。
S (共享锁) X (排他锁)
S (共享锁) 兼容 冲突
X (排他锁) 冲突 冲突

(2) 行锁类型:

SQL 行锁类型 说明
insert,update,delete ... 排他锁 自动加锁
select 不加任何锁
select ... lock in share mode 共享锁 需要手动select之后加上lock in share mode
select ... for update 排他锁 需要手动在select之后for update

(3) 默认情况下,InnoDB在 REPEATABLE READ事务隔离级别运行,InnoDB使用 next-key锁进行搜索和索引扫描,以防止幻读。

  • 针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁。
  • InnoDB的行锁是针对于索引加的锁,不通过索引条件检索数据,那么!nnoDB将对表中的所有记录加锁,此时 就会升级为表锁

(4) 查看意向锁及行锁的加锁情况:

sql 复制代码
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
2. Gap Lock(间隙锁)

(1) Gap Lock 称为间隙锁,只存在于可重复读隔离级别,目的是为了解决可重复读隔离级别下幻读的现象。

(2) 假设,表中有一个范围 id 为(3,5)间隙锁,那么其他事务就无法插入 id = 4 这条记录了,这样就有效的防止幻读现象的发生。

间隙锁虽然存在 X 型间隙锁和 S 型间隙锁,但是并没有什么区别,间隙锁之间是兼容的,即两个事务可以同时持有包含共同间隙范围的间隙锁,并不存在互斥关系,因为间隙锁的目的是防止插入幻影记录而提出的。

3. Next-Key Lock(临键锁)

(1) Next-Key Lock 称为临键锁,是 Record Lock + Gap Lock 的组合,锁定一个范围,并且锁定记录本身。

(2) 假设,表中有一个范围 id 为(3,5] 的 next-key lock,那么其他事务即不能插入 id = 4 记录,也不能修改 id = 5 这条记录。

(3) 所以,next-key lock 既能保护该记录,又能阻止其他事务将新纪录插入到被保护记录前面的间隙中。

4. 结论

默认情况下,InnODB在 REPEATABLE READ事务隔离级别运行,InnoDB使用 next-key 锁进行搜索和索引扫描,以防止幻读。

  • 索引上的等值查询(唯一索引),给不存在的记录加锁时,优化为间隙锁 。
  • 索引上的等值查询(普通索引),向右遍历时最后一个值不满足查询需求时,next-keylock退化为间隙锁。
  • 索引上的范围查询(唯一索引)--会访问到不满足条件的第一个值为止。

五、InnoDB引擎

1. 逻辑存储结构

(1) 表空间(对应 ibd 文件)

MySQL 实例级的存储容器,一个 MySQL 实例可以对应多个表空间,用于存储表的记录、索引等数据。

(2) 段(Segment)

① 是表空间的下一级结构,用于管理多个区

② 分类:

  • 数据段(Leaf node segment):对应 B + 树的叶子节点
  • 索引段(Non-leaf node segment):对应 B + 树的非叶子节点
  • 回滚段(Rollback segment):用于事务回滚相关的版本管理。

(3) 区(Extent)

表空间的单元结构,每个区的大小为 1MB,默认 InnoDB 页大小为 16KB,因此 1 个区包含 1MB ÷ 16KB = 64个连续的页

(4) 页(Page)

① InnoDB 存储引擎**磁盘管理的最小单元,**每个页的大小默认 16KB;

② 为了保证页的物理连续性,InnoDB 存储引擎每次会从磁盘一次性申请 4-5 个区

(5) 行

① InnoDB 的数据是按 "行" 为单位存放的;

② 行的隐藏列(事务相关):

  • trx_id:事务 ID,每次修改记录时,会将对应事务的 ID 存入trx_id隐藏列;
  • roll_pointer(回滚指针):修改记录时,会把数据的旧版本写入 undo 日志;回滚指针相当于 "指针",可通过它找到记录修改前的信息。
2. 整体架构
3. 内存架构

(1) Buffer Pool:

① 缓冲池是内存中的一个区域,它可以缓存磁盘上经常操作的真实数据,在执行增删改查操作时,先操作缓冲中的数据(若缓冲池没有数据,则从磁盘加载并缓存),然后以一定频率刷新到磁盘,从而减少磁盘 IO,加快处理速度。

② 缓冲池以 Page 为单位,采用链表数据结构管理 Page。根据状态,将 Page 分为三种类型:

  • free page:空的 page,未被使用。
  • clean page:被使用 page,数据没有被修改过。
  • dirty page:脏页,被使用 page,数据被修改过,也就是缓冲与磁盘的数据产生了不一致。

(2) Change Buffer:

① 更改缓存(针对于非唯一二级索引使用),在执行 DML 语句时,如果该数据 Page 没有在 Buffer Pool 里,不会直接把磁盘进行操作,而会将变更暂存在更改缓存区 Change Buffer 中,在未来数据被读取时,再将数据合并并更新到 Buffer Pool,再将合并后的数据刷新到磁盘中。

② 与主键索引不同,二级索引通常是非唯一的,并且以相对随机的顺序插入二级索引,同样,删除和更新可能会影响索引中不相邻的二级索引页,如果每一次都操作磁盘,会造成大量的随机 IO,有了 ChangeBuffer 之后,我们可以在缓存池中进行合并处理,减少磁盘 IO。

(3) Adaptive Hash Index:

① 自适应 hash 索引,用于优化对 Buffer Pool 数据的查询。InnoDB 存储引擎会监控对表上各索引页的查询,如果观察到 hash 索引可以提升速度,则建立 hash 索引,称之为自适应 hash 索引。

② 自适应哈希索引,无需人工干预,是系统根据情况自动完成。

(4) Log Buffer:

① 日志缓冲区,用来保存要写入到磁盘中的 log 日志数据(redo log、undo log)。默认大小为 16MB,日志缓冲区的日志会定期刷新到磁盘中。如果需要更新、插入或删除许多行的事务,增加日志缓冲区的大小可以节省磁盘 I/O。

② 参数:

  • Innodb_log_buffer_size:缓冲区大小
  • innodb_flush_log_at_trx_commit:日志刷新到磁盘时机

1: 日志在每次事务提交时写入并刷新到磁盘

0: 每秒将日志写入并刷新到磁盘一次

2: 日志在每次事务提交后写入,并每秒刷新到磁盘一次

4. 磁盘结构

(1) System Tablespace:

① 是更改缓冲区的存储区域;若表创建在该空间(而非单表 / 通用表空间),还会包含表、索引数据。(在 MySQL5.x 版本中还包含 InnoDB 数据字典、undolog 等)

② 参数:innodb_data_file_path

(2) File-Per-Table Tablespaces:

① 每个表的文件表空间包含单个 InnoDB 表的数据和索引,并存储在文件系统上的单个数据文件中。

② 参数:innodb_file_per_table

(3) General Tablespaces:通用表空间

① 创建通用表空间:

sql 复制代码
CREATE TABLESPACE xxx ADD DATAFILE 'file_name' ENGINE = engine_name;

② 对应表创建语句:

sql 复制代码
CREATE TABLE xxx ... TABLESPACE ts_name;

(4) Undo Tablespaces:

撤销表空间,MySQL 在实例初始化时会自动创建两个默认的 undo 表空间(初始大小 16MB),用于存储 undo log 日志。

(5) Temporary Tablespaces:

InnoDB 使用设备临时表空间和全局临时表空间,存储用户创建的临时表等数据。

(6) Doublewrite Buffer Files:

① 双写缓冲,InnoDB 引擎将数据页从 Buffer Pool 刷新到磁盘前,先将数据页写入双写缓冲文件中,便于系统异常时恢复数据。

② 示例文件:

  • #ib_16384.0.dblwr
  • #ib_16384.1.dblwr

(7) Redo Log:

① 重做日志,是用来实现事务的持久性。该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log)。前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志中,用于在数据页写到磁盘时发生错误时,进行数据恢复使用。

② 以循环方式写入重做日志文件,涉及两个文件:

  • ib_logfile0
  • ib_logfile1
5. 后台线程
6. 事务原理

(1) 事务:一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。

(2) 特征:

  • 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
  • 一致性(Consistency) :事务完成时,必须使所有的数据都保持一致状态。
  • 隔离性(lsolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
  • 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。

(3) 特性原理分类图:

7. redo log

(1) 重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性

(2) 该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log file),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志文件中,用于在刷新脏页到磁盘,发生错误时,进行数据恢复使用。

(3) Buffer Pool在产生脏页数据的时候,会先将数据存储到 redo log buffer 再存储到 redo log 中进行磁盘持久化存储,在内存出现异常(比如突然断电)时,通过redo log中持久化的数据进行回滚。过程如下图:

(4) redo log 分为 2 部分:

  • 内存里的「redo log 缓冲」:临时存一下修改记录(相当于 "草稿纸");
  • 磁盘里的「redo log 文件」:长期存修改记录(相当于 "正式笔记本")。

(5) 工作流程像这样:

比如你修改了一条数据 → 先把 "我改了啥、怎么改的" 记到「redo log 缓冲」里 → 事务提交时,把缓冲里的记录写到「redo log 文件」里 → 之后数据库再慢慢把修改好的数据(存在内存缓存里的 "脏数据")刷到磁盘。

要是中途突然断电:重启后,数据库会看「redo log 文件」里的记录,把没来得及刷到磁盘的修改,重新恢复出来。

Q:redo log 要写到磁盘,数据也要写磁盘,为什么要多此一举?

A:redo log 是 "顺序写",磁盘处理起来特别快; 而直接写数据是 "随机写",得先找到数据在磁盘里的位置,再修改,速度很慢。

8. undo log

(1) 回滚日志,用于记录数据被修改前的信息,作用包含两个:提供回滚 和 MVCC(多版本并发控制)。

(2) undo log 和 redo log 记录物理日志不一样,它是逻辑日志。可以认为当 delete 一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当 update 一条记录时,它记录一条对应相反的 update 记录。当执行 rollback 时,就可以从 undo log 中的逻辑记录读取到相应的内容并进行回滚。

(3) Undo log 销毁:undo log 在事务执行时产生,事务提交时,并不会立即删除undol0g,因为这些日志可能还用于 MVCC。

(4) Undo log 存储:undo log 采用段的方式进行管理和记录,存放在前面介绍的 rollback segment 回滚段中,内部包含1024个 undo log segment.

六、MVCC

1. 当前读:

读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。对于我们日常的操作,如:select...lock in share mode(共享锁),select... for update、update、insert、delete(排他锁)都是一种当前读。

2. 快照读:

简单的select(不加锁)就是快照读,快照读,读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。

  • Read committed:每次select,都生成一个快照读。
  • Repeatable Read:开启事务后第一个select语句才是快照读的地方。
  • Serializable:快照读会退化为当前读。
3. MVCC:

全称 Multi-Version Concurrency Control,多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,快照读为 MVSQL 实现MVCC提供了一个非阻塞读功能。MVCC的具体实现,还需要依赖于数据库记录中的三个隐式字段、undo log日志、read View。

4. 三个隐藏字段
5. undo log

(1) 回滚日志,在insert、update、delete的时候产生的便于数据回滚的日志。

(2) 当insert的时候,产生的undolog日志只在回滚时需要,在事务提交后,可被立即删除。

(3) 而update、delete的时候,产生的undo log日志不仅在回滚时需要,在快照读时也需要,不会立即被删除。

(4) 那么何时删除?

① 事务提交后

  • 对于INSERT操作,事务提交后,undo log可以被立即删除,因为不再需要用于回滚。例如:你新增了一条 "订单记录",事务提交后,既不需要回滚(回滚是删这条记录,但提交了就不用回滚了),也不会有其他事务需要读这条记录的 "旧版本"(因为它是刚新增的,没有更早的版本),所以 undo log 没用了,直接删。

**Q:**为什么说没有旧版本?旧版本不是没有插入之前的吗?

A: 这里的旧版本指的是**"这条新插入记录本身的旧版本" ,**不是 "表在插入前的状态",可以理解成行锁和表锁的关系。

  • 对于UPDATEDELETE操作,undo log不会立即被删除,因为它们可能在后续的快照读取中被使用。例如:你把 "订单金额从 100 改成 200",事务提交后,可能有其他事务需要看 "金额 100" 这个旧版本(比如查 5 分钟前的订单状态),这时候得靠 undo log 调出旧数据,所以它还能用,得留着。

② 快照读取结束

  • 当所有依赖于该undo log的快照读取操作结束后,undo log才会被删除。这意味着如果有一个事务正在进行快照读取,并且依赖于某个undo log,那么这个undo log会一直保留直到该事务结束。

(5) undo log 版本链

6. readview

(1) ReadView(读视图)是 快照读 SOL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务(未提交的)id。

(2) ReadView中包含了四个核心字段:

字段 含义
m_ids 当前活跃的事务ID集合
min_trx_id 最小活跃事务ID
max_trx_id 预分配事务ID,当前最大事务ID+1(因为事务ID是自增的)
creator_trx_id ReadView创建者的事务ID(即 "当前要读数据的那个事务" 的 ID)

(3) 版本链数据访问规则

① 规则 1:(trx_id = creator_trx_id → 能访问):

这里的creator_trx_id就是表格里的 "ReadView 创建者的事务 ID"------ 如果某数据版本的修改者 ID,和 "当前读数据的事务 ID" 一致,说明是自己改的,能访问。

② 规则 2:(trx_id < min_trx_id → 能访问):

min_trx_id是 "最小活跃事务 ID"------ 如果某数据版本的修改者 ID,比 "当前最小编号的活跃事务" 还小,说明这个修改事务早于所有活跃事务完成提交,能访问。

③ 规则 3:(trx_id > max_trx_id → 不能访问):

max_trx_id是 "预分配事务 ID(当前最大 ID+1)"------ 如果某数据版本的修改者 ID,比这个预分配 ID 还大,说明这个修改事务是在 ReadView 创建之后才启动的(事务 ID 自增),还没完成,不能访问。

④ 规则 4:(min_trx_id ≤ trx_id ≤ max_trx_id 且 trx_id 不在 m_ids 里 → 能访问):

  • min_trx_id/max_trx_id限定了 "当前活跃事务的 ID 范围";
  • m_ids是 "当前活跃的事务 ID 集合"------ 如果某数据版本的修改者 ID 在这个范围内,但不在活跃集合里 ,说明这个修改事务已经提交了(从活跃状态变成已完成),能访问。

(4) 原理分析(RC 级别)

之所以事务3,事务4都是活跃事务,是因为这个表格执行顺序是从上至下的,在事务5 第一次查询 id 为 30 的记录时,事务3 和 事务4 都没有结束。在事务5 第二次查询 id 为 30 的记录时,事务3 已经结束了

(5) 原理分析(RR 级别)

七、MySQL 管理

1. 系统数据库介绍

Mysql数据库安装完成后,自带了一下四个数据库,具体作用如下:

数据库 含义
mysql 存储MySQL服务器正常运行所需要的各种信息(时区、主从、用户、权限等)
information_schema 提供了访问数据库元数据的各种表和视图,包含数据库、表、字段类型及访问权限等
performance_schema 为MySQL服务器运行时状态提供了一个底层监控功能,主要用于收集数据库服务器性能参数
sys 包含了一系列方便 DBA和开发人员利用 performance_schema性能数据库进行性能调优和诊断的视图
2. 常用工具

(1) mysql

(2) mysqladmin

(3) mysqlbinlog

(4) mysqlshow

(5) mysqldump

(6) mysqlimport / source

相关推荐
嘟嘟w2 小时前
MySQL 中 InnoDB 支持的四种事务隔离级别名称,以及逐级之间的区别?
数据库·mysql
小龙2 小时前
【学习笔记】模型的权重数据到底是干嘛的
人工智能·笔记·学习·权重
星光一影2 小时前
智慧停车与充电一体化管理平台:打造城市出行新生态
mysql·vue·能源·springboot·uniapp
代码游侠2 小时前
应用——Linux FIFO(命名管道)与I/O多路复用
linux·运维·服务器·网络·笔记·学习
好奇龙猫2 小时前
大学院-筆記試験練習:数据库(データベース問題訓練) と 软件工程(ソフトウェア)(1)
学习·大学院
studyForMokey2 小时前
【跨端技术】React Native学习记录一
javascript·学习·react native·react.js
代码游侠2 小时前
复习——网络编程基础
linux·服务器·网络·笔记·网络协议
先生沉默先2 小时前
串口通信学习,使用winform读取串口发送数据,(2)
学习·c#·串口
YJlio2 小时前
CSDN年度总结2025:技术逐梦不止,步履坚定向前
windows·学习·流程图