mysql教程笔记(四)-锁和innoDB存储引擎
1.mysql的锁
MySQL 的锁机制是保证并发访问数据一致性与完整性的核心组件。
LOCK IN SHARE MODE
(共享锁):允许多个事务同时读取同一行数据,但阻止其他事务修改这些行或加排他锁。SELECT ... FOR SHARE;
(在可重复读或读已提交隔离级别下显式加 S 锁)
FOR UPDATE
(排他锁):不仅阻止其他事务修改锁定的行,还阻止其他事务读取。INSERT, UPDATE, DELETE 操作会自动对涉及的行加 X 锁;SELECT ... FOR UPDATE;
(显式加 X 锁)
1.锁的分类
1. 按锁粒度分类
1全局锁
锁定整个数据库实例,阻止所有写操作(DML、DDL)。
FLUSH TABLES WITH READ LOCK;// 加锁
UNLOCK TABLES; //释放锁
2 表级锁
1.表锁
存储引擎支持:MyISAM、MEMORY 使用表级锁;InnoDB 支持表级锁但默认使用行级锁。
InnoDB 也有表级锁,但通常是 MySQL 内部使用的:比如 ALTER TABLE
操作会加表锁。
锁类型:
- 表共享读锁(Table Read Lock):允许其他事务读取,但阻塞写操作 。
- 表独占写锁(Table Write Lock):阻塞其他事务的读和写操作。
LOCK TABLES users READ; -- 共享锁,其他只能读
LOCK TABLES users WRITE; -- 排他锁,其他不能读也不能写
UNLOCK TABLES;
2.元数据锁
元数据锁(Metadata Lock,简称 MDL) 是一种用于保护数据库对象(如表、视图、存储过程等)结构的锁机制。它确保在对某个对象进行操作时,其元数据(例如表结构)不会被并发修改,从而避免数据不一致或操作异常。元数据锁的加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。 MDL 在 DDL(数据定义语言)和 DML(数据操作语言)之间提供互斥控制,防止冲突。
更简单的理解方式就是:在对表进行增删查改操作的时候,不能更改表的结构
2.1元数据锁的类型
1.共享锁(Shared Lock, S):
用途:DML操作(如SELECT
、UPDATE
)申请,允许多个事务并发读取表结构。
兼容性:多个共享锁可共存,但与排他锁互斥。
执行时机:执行 SELECT
、INSERT
、UPDATE
、DELETE
时获取共享锁;
2.排他锁(Exclusive Lock, X):
用途:DDL操作(如ALTER TABLE
)申请,独占表结构修改权。
兼容性:与所有锁类型互斥,确保操作期间无并发访问。
执行时机:执行 ALTER TABLE
、DROP TABLE
、RENAME TABLE
等 DDL 操作时获取排他锁;
-
锁队列优先级:
写锁(排他锁)优先于读锁(共享锁)获取,可能导致DML操作被DDL阻塞。
3.如何查看元数据锁
select object_type, object_schema, object_name, lock_type, lock_duration, lock_status
from performance_schema.metadata_locks;
3.意向共享锁
InnoDB 引擎中用于协调行级锁与表级锁的表级锁机制,旨在提升多粒度锁管理的效率。它的作用是:告诉其他事务,我(当前事务)打算在表中的某些行上加行锁!
多个事务可以同时持有同一表上的 IS 锁,因为这些事务都只是表明了读取某些行的意图,并没有实际锁定任何特定的行
- 避免每次操作都要扫描所有行锁来判断是否有冲突;
- 允许多个事务同时持有 IS 锁,表示它们都只读取数据。
1. 意向锁的类型
- 意向共享锁(IS):事务打算在表中的某些行上加 S 锁
与表锁共享锁(read)兼容,与表锁排他锁(write)互斥
示例:由语句 select … lock in share mode 添加 - 意向排他锁(IX):事务打算在表中的某些行上加 X 锁
与表锁共享锁(read)兼和表锁排他锁(write)均互斥,意向锁之间不会互斥
示例:由 insert、update、delete、select … for update 添加
当一个事务需要对表中的某行加 S 锁时,会先对该表加 IS 锁;需要对表中的某行加 X 锁时,会先对该表加 IX 锁。这些都是由 InnoDB 自动管理的,我们通常感知不到。
// 加 IS 锁
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
// 加 IX 锁
UPDATE users SET name = 'Tom' WHERE id = 1;
InnoDB 中各种锁之间的兼容性决定了锁冲突的可能性。下表展示了意向锁与其他锁之间的兼容性关系:
意向锁之间不会互斥,怎么理解呢
理解意向锁之间不互斥的关键在于了解不同类型的意向锁以及它们的作用:
意向共享锁 (IS):当事务想要获取一个表上的共享锁(读取数据)时,它会先申请一个意向共享锁。这个锁表示事务有意向在表的某一行或某些行上获取共享锁
意向排他锁 (IX):当事务想要获取一个表上的排他锁(写入数据)时,它会先申请一个意向排他锁。这个锁表示事务有意向在表的某一行或某些行上获取排他锁
例子说明:
- 假设事务 T1 获取了一个表上的 IS 锁,这表示 T1 有意向读取该表的某些行
- 另一个事务 T2 也可以获取该表上的 IX 锁,表示 T2 有意向更新该表的某些行
- 在这种情况下,T1 和 T2 都可以在不冲突的情况下进行操作,只要他们不尝试在同一行上执行互斥的操作
IS | IX | S | X | |
---|---|---|---|---|
IS | ✔ | ✔ | ✔ | ✖ |
IX | ✔ | ✔ | ✖ | ✖ |
S | ✔ | ✖ | ✔ | ✖ |
X | ✖ | ✖ | ✖ | ✖ |
3 行级锁
MySQL的行锁(Row-Level Lock)是InnoDB存储引擎实现事务隔离性的核心机制之一,用于控制对表中特定行的并发访问。确保事务对特定行的修改在事务提交前不被其他事务干扰,避免数据不一致。锁定单行或范围,而非整个表。
实现方式
行锁依附于索引而存在,也就是说, 行锁是在加索引上而不是行上的
InnoDB 通过索引项加锁实现行级锁,若查询未使用索引则退化为表级锁 。
锁类型:
1.共享锁(Shared Lock, S Lock)
用途:允许事务读取行,但阻止其他事务对该行加排他锁。
兼容性:多个事务可同时持有共享锁,但无法获取排他锁。
SELECT * FROM table_name WHERE id = 1 FOR SHARE;
2.排他锁(Exclusive Lock, X Lock)
用途:允许事务修改行,阻止其他事务加共享锁或排他锁。
兼容性:仅允许一个事务持有排他锁。
UPDATE table_name SET column = value WHERE id = 1;
3.间隙锁(Gap Lock)
作用:间隙锁没有共享和排他的概念,锁定索引记录之间的“间隙”,防止其他事务插入新行,避免幻读(Phantom Read)。
适用场景:在REPEATABLE READ
隔离级别下,默认启用间隙。
示例:
-- 表数据有 id = 1,3,5
select * from table1 where id>3 and id<5 for update; ?-- 会锁住 (3,5) 范围, 防止插入4
4.临键锁(Next-Key Lock)
- 定义:共享锁/排他锁与间隙锁的组合,覆盖索引记录及其间隙。
- 作用:防止幻读和不可重复读,是InnoDB默认的锁类型(在
REPEATABLE READ
下)。 - 示例:
-- 表数据有 id = 1,3,5 select * from table1 where id>3 and id<=5 for update; ?-- 会锁住 (3,5] 范围
4. 页级锁
- 特点:锁定数据页(介于行级与表级之间),BDB 存储引擎支持,MySQL 中较少使用
2. innoDB引擎
MySQL 数据文件的默认保存位置因操作系统而异:
-
Windows 系统
- 默认路径为
C:\ProgramData\MySQL\MySQL Server X.X\Data
,其中X.X
为 MySQL 版本号,ProgramData
为隐藏文件夹。 - 例如 MySQL 8.0 的数据文件位于:
C:\ProgramData\MySQL\MySQL Server 8.0\Data
3。
- 默认路径为
-
Linux 系统
- 默认路径为
/var/lib/mysql
。
- 默认路径为
-
Mac OS
- 默认路径为
/usr/local/mysql/data
。
- 默认路径为
每个数据库都有一个与其同名的文件夹,而这个文件夹中包含了该数据库的所有表的文件,而其中的文件代表了数据库中的表。
文件类型与存储引擎
不同存储引擎生成的文件类型不同:
-
InnoDB 引擎
- 默认模式:启用
innodb_file_per_table
时,每个表对应独立的.ibd
文件(存储数据和索引),表结构定义在.frm
文件中。 - 共享表空间模式:未启用
innodb_file_per_table
时,数据集中存储在ibdata1
文件中。
- 默认模式:启用
-
MyISAM 引擎
- 每个表生成三个文件:
.frm
(表结构)、.MYD
(数据)、.MYI
(索引)3
- 每个表生成三个文件:
1. innoDB引擎的逻辑存储
InnoDB 的逻辑存储结构采用分层设计,通过表空间、段、区、页、行等层级管理数据,兼顾性能与扩展性。
InnoDB 的逻辑存储结构分为多个层级,从大到小依次如下:
层级 | 描述 |
---|---|
表空间(Tablespace) | 最大的逻辑存储单位,可以包含一个或多个数据文件(ibd 文件),支持共享表空间和独立表空间(innodb_file_per_table=ON) |
段(Segment) | 表空间中的逻辑容器,每个索引通常由两个段组成:叶子节点段(Leaf Node Segment)和非叶子节点段(Non-Leaf Node Segment) |
区(Extent) | 每个区大小为 1MB(默认),由 64 个连续的数据页组成,用于分配和管理磁盘空间 |
页(Page) | InnoDB 管理数据的最小单位,默认大小为 16KB,页中可以存储多个行记录 |
行(Row) | 存储在页中的实际数据记录 |
1. 表空间(Tablespace)
- 是 InnoDB 的最高层逻辑结构;
- 默认情况下,所有表都存放在系统表空间(
ibdata1
)中; - 如果启用
innodb_file_per_table=ON
,则每个表都有一个独立的.ibd
文件; - 支持压缩、加密等高级特性。
特别性 | 独立表空间 | 共享表空间 |
---|---|---|
存储位置 | 每个表对应独立的.ibd 文件 | 所有表数据存储在ibdata1 文件中 |
空间管理 | 表空间大小可收缩,支持动态扩展 | 空间不可收缩,持续增长 |
备份与恢复 | 支持单表备份(如mysqldump 或文件复制) | 需整体备份整个共享表空间 |
性能影响 | 减少锁冲突,提升并发写入性能 | 大表操作可能导致全局锁冲突 |
表空间碎片 | 每个表独立管理,碎片不影响其他表 | 碎片累积可能影响整体性能 |
2. 段(Segment)
段(Segment)由多个区组成,但不要求区之间连续。
1. 段的类型
- 数据段(Leaf Node Segment):
对应 B+ 树的叶子节点,存储实际数据行(如表数据)。 - 索引段(Non-leaf Node Segment):
对应 B+ 树的非叶子节点,存储索引键值 。 - 回滚段(Rollback Segment):
存储事务的回滚日志(Undo Log),用于多版本并发控制(MVCC)
3. 区(Extent)
区由连续的物理页组成,确保B+树索引的相邻节点在磁盘上也连续存储。这使得范围查询(如SELECT * FROM table WHERE id BETWEEN 1 AND 100
)能通过顺序IO(Sequential I/O)高效读取,而非随机IO(Random I/O),从而大幅提升性能 。
InnoDB每次从磁盘申请4-5个区,以保证区的连续性。这种批量分配策略减少了频繁的小块空间请求,降低了管理开销并减少碎片化风险
1.区的核心
- 大小固定为 1MB;
- 由 64 个连续的页组成;
- InnoDB 在分配空间时以区为单位进行管理;
- 对于大型表,还可以使用“碎片区”(Fragment Page)来减少空间浪费。
1.区的分类
- 空闲区(FREE):
完全未被使用的区,可用于后续数据分配。 - 碎片区(FREE_FRAG / FULL_FRAG):
- FREE_FRAG:部分页面被使用,剩余页面可用。
- FULL_FRAG:所有页面已被占用,但未形成完整区。
碎片区主要用于小表或数据量较少的场景,避免为少量数据分配完整区(1MB)造成浪费
4. 页(Page)
页是 InnoDB 中最小的 I/O 单位,默认大小为 16KB,可通过配置项 innodb_page_size
设置为 4KB / 8KB / 16KB / 32KB / 64KB(必须为4kb的倍数);
1.为什么要使用页?
1.减少磁盘访问次数
磁盘读写以扇区(512B)为单位,但单次 I/O 操作需寻道和旋转延迟,频繁小数据量读写效率极低。以 16KB 页为单位批量读写,相当于一次 I/O 处理 32 个扇区,显著减少磁盘访问次数。
2.局部性原理优化
相邻数据可能被连续访问(如范围查询),加载整页可预读后续数据到内存,减少后续查询的磁盘 I/O。
2.页类型包括:
- 数据页(B-tree Node)
- undo 日志页
- 系统页
- 事务页
- 插入缓冲空闲列表页等
- 页中还包含一些元数据,如页头、页尾、空闲空间指针等。
5. 行(Row)
- 实际存储的数据记录;
- 行格式包括:
REDUNDANT
(旧版本)COMPACT
DYNAMIC
COMPRESSED
- 不同的行格式影响存储效率和功能支持(如变长字段、溢出列等);
- 行中还包含隐藏字段:
DB_ROW_ID
(行 ID)DB_TRX_ID
(事务 ID)DB_ROLL_PTR
(回滚指针)
2.innoDB引擎的内存结构
InnoDB引擎的内存结构主要包括以下核心组件:
1. 缓冲池(Buffer Pool)
- 作用:缓存数据页(默认16KB),减少磁盘I/O,提升访问效率。通常分配宿主机内存的60%-80% 。
- 管理机制:
- LRU链表:分为 New Sublist(占63%-75%)和 Old Sublist(占25%-37%)。新页从MidPoint插入,频繁访问的页向New头部移动,不活跃页逐步淘汰 。
- 三类链表:
- Free List:管理空闲页(free page)。
- LRU List:管理正在使用的干净页(clean page)和脏页(dirty page)。
- Flush List:管理需刷新到磁盘的脏页 。
2. 日志缓冲区(Log Buffer)
- 作用:缓存Redo Log日志,减少磁盘刷写频率。默认大小16MB,可通过
innodb_log_buffer_size
调整 。 - 刷新策略:通过
innodb_flush_log_at_trx_commit
参数控制刷盘频率(如事务提交时刷盘)。
3. 写缓冲区(Change Buffer)
- 作用:缓存对非唯一辅助索引的更新操作(INSERT/UPDATE/DELETE),减少随机IO。默认占用Buffer Pool的25%,最大可配置至50% 。
- 合并机制:当数据页被读取时,Change Buffer中的变更会合并到Buffer Pool中 。
4. 自适应哈希索引(Adaptive Hash Index)
- 作用:自动为频繁访问的页生成哈希索引,加速查询 。
- 特点:仅对热点数据生效,无需手动维护 。
5. 内存堆(Memory Heap)
- 实现:通过
mem_heap_t
管理内存块链表,适用于短周期小内存分配场景,减少碎片化 。
6. 其他组件
- 插入缓冲(Insert Buffer):旧版特性,已被Change Buffer替代 。
- 后台线程:负责刷新脏页、合并Change Buffer等任务 。
3.InnoDB 中的“叶子结点”到底是什么?
InnoDB 是按 页(Page) 来管理磁盘 I/O 的,默认大小为 16KB,也就是说:
- 每个 B+ 树的节点(包括叶子节点)都对应一个磁盘页(page)。
- 一页中可以包含多个行记录(根据每条记录大小不同,可能几十到几百条不等)
在 InnoDB 的 B+ 树索引中:
- 叶子节点 是树的最后一层节点,它存储了真正的数据记录。
- 每个叶子节点对应一个 磁盘页(Page),默认大小为
16KB
。
所以:叶子结点 = 一页数据(Page)
1.既然一页里有很多行,怎么做到“精确定位”?
这是关键点!虽然一页中有多个记录,但通过以下机制可以实现高效的精确定位:
1. 通过索引键精确查找
当你执行类似:
SELECT * FROM users WHERE id = 100;
- InnoDB 会从 B+ 树根节点开始查找,逐层定位,直到找到对应的叶子节点页。
- 在该页内部,使用 二分查找 + 线性扫描 快速定位具体的记录。
2. 叶子页内记录有序排列
每个叶子页中的记录是按照索引键排序的(如主键或二级索引列),这使得:
- 可以快速判断某条记录是否存在于该页。
- 支持范围查询、顺序扫描等操作。
3. 记录之间有偏移量信息
InnoDB 在页中维护了记录的偏移量数组(称为 页目录),可以通过这些偏移量快速跳转到某条记录。
2.类比理解
你可以把一个叶子页想象成一个“电话簿”,其中:
- 每一页是一个字母段(如 A–C)。
- 页面内有多个联系人(记录),按姓名排序。
- 当你要找 "Alice",先找到 A–C 这页,再在页内查找 Alice。
3.那你说的“数据段(Leaf Segment)和非叶段(Non-leaf Segment)”又是什么?
这是 InnoDB 管理 B+ 树空间的一种方式。
段(Segment)的概念
InnoDB 将 B+ 树分为两个逻辑段:
段类型 | 对应内容 |
---|---|
FSP_SEG_INDEX | 表示一个索引(聚簇索引或二级索引) |
其中细分: | |
FSEG_FULL / FSEG_FREE / FSEG_NOT_FULL | 描述页的使用状态 |
更细粒度: | |
FSEG_HEADER_LEAF | 叶子节点段(存储实际数据行) |
FSEG_HEADER_INTERNAL | 非叶子节点段(存储索引键值和指针) |
简单理解:
- 叶子段(Leaf Segment):包含所有叶子节点页,即真正存储数据记录的页。
- 非叶段(Non-leaf Segment):包含所有非叶子节点页,即只存索引键和指针。
4.总结:页 vs 段
名称 | 类型 | 大小 | 作用 | 关系 |
---|---|---|---|---|
Page(页) | 物理单位 | 默认 16KB | 最小 I/O 单位,存储数据记录或索引键 | 多个页组成一个段 |
Segment(段) | 逻辑单位 | 不定 | 管理一组页,如叶子页集合、非叶子页集合 | 段由多个页构成 |
所以你问的问题是完全正确的:
- 叶子结点是一“页”
- 而“叶子段”是一组这样的“页”的集合
5.类比理解
你可以把整个 B+ 树想象成一本书:
- 每一页(Page):是一个具体的书页,上面写满了数据(记录或索引键)
- 章节(Segment):
- 第一章是“目录页”(非叶子段)
- 第二章是“正文页”(叶子段)
6.总结回答你的问题
问题 | 回答 |
---|---|
叶子结点到底是页还是段? | 叶子结点是一页数据,而多个叶子页组成“叶子段” |
数据段和索引段的关系? | “数据段”通常指叶子段,“索引段”可以泛指整个 B+ 树结构或非叶段 |
段和页的关系? | 段是逻辑单位,由多个物理页组成 |
为什么会有“段”的概念? | 为了更高效地管理不同类型的页(如叶子页 vs 非叶子页) |
如果你对某个具体的页结构、段分配策略、或如何查看页/段信息感兴趣,我可以继续带你一步步分析 .ibd
文件的内部结构。