一、锁
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_logfile0ib_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: 这里的旧版本指的是**"这条新插入记录本身的旧版本" ,**不是 "表在插入前的状态",可以理解成行锁和表锁的关系。
- 对于
UPDATE和DELETE操作,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
