# 纲要 > 主干纲要、Hint/线索/路标 - **上锁思想**: - 乐观锁 - 悲观锁 - **粒度分**: - 全局锁 - **表级锁** - 意图锁 - 元数据锁 - **行级锁** - 记录锁 - 间隙锁 - 临键锁 - **按功能分**:(表级、行级锁均分为下列两种) - 共享锁、独占锁 %% # Q&A #### 已明确 #### 待明确 > 当下仍存有的疑惑 **❓<font color="#c0504d"> 有什么问题?</font>** %% <br><br> # 数据库上锁思想 就**上锁思想**而言(非具体锁),可分为两类: - **乐观锁**:**操作的时候不加锁**,而在**更新数据时**进行 "**==版本校验==**" (利用版本号或时间戳),如果冲突了就拒绝修改。 - 适用于 "**读多写少**" 的场景,写操作少,故**假设数据冲突概率低**。 - **悲观锁**:**操作数据之前==先对数据加锁==**(利用数据库提供的 **==具体锁机制==**,例如行锁或者表锁),确保其他事务无法访问。 - 适用于 "**写多读少,并发冲突多**" 的场景。 这两种 "**锁**" 只是**一种抽象思想**,而非指代数据库提供的具体锁机制。 %% - **乐观锁**:基于乐观假设(并发环境下对共享资源的访问冲突少见),采用 "**冲突检测机制**"(版本号或 CAS 操作),**更新共享数据时不加锁**,仅在 "**提交时**" 进行冲突检测,**若冲突则回滚操作**。 - **悲观锁**:基于悲观假设(并发环境中**对共享资源的访问冲突**频繁常见),因此**每次访问共享资源时都必须==直接加锁互斥==**。 %% <br> ## 乐观锁 「**乐观锁**」:基于乐观假设(并发环境中**对共享资源的访问冲突**少见),因此: - 在更新共享资源时不加锁,仅在**提交修改**时进行 **==冲突检测==**。 - 若没有检测到冲突,则修改可成功提交;如果检测到冲突,则会 **==回滚操作==** 并重试。 > [!NOTE] 乐观锁实际上 "没有锁",而是基于 "**冲突检测机制**"(版本号或 CAS 操作) 优点: - **高并发场景效率高**:适用于**读多写少**的场景。在大部分操作不冲突的情况下,乐观锁可以减少锁的开销和阻塞时间,提升并发性能。 - **避免锁的长期持有**:因为不加锁,多个线程可以并行操作,只有在提交时才检查冲突。 缺点: - **不适合写多的场景**:如果冲突频繁发生,回滚和重试的代价会很高,导致系统性能下降。 - **实现复杂度**:需要额外的机制(如版本号、CAS 等)来检测和解决冲突。 适用场景: - 数据库中的**无锁更新**:在数据库中,乐观锁常用于**非阻塞事务**,如在`UPDATE`操作时,使用版本号或时间戳来检测数据是否被修改。 - **分布式系统**:在分布式环境中,多个节点对共享数据的更新往往使用乐观锁,以减少锁竞争带来的性能问题。 > [!example] 乐观锁使用场景 > > ![[_attachment/02-开发笔记/08-数据库/MySQL 相关/MySQL 锁.assets/IMG-MySQL 锁-A1EF025DF9275E61906F027E32FA709B.png|492]] ### 乐观锁的实现思路——版本号机制 "版本号机制" 是**乐观锁常见的实现方式**: 1. 读取共享资源,同时**读取资源的==版本号或状态标识==**; 2. 执行更新操作(期间**不加锁**),准备提交更改; 3. 提交更改前,**检查版本号或状态是否与预期一致**: - 若版本号未变化,**允许提交**。 - 若版本号已变化,**==回滚==操作**。 > [!NOTE] 手动实现乐观锁:基于 "版本号" 字段 > > ```sql > SELECT id, name, version FROM users WHERE id = 1; > > UPDATE users > SET name = 'new_name', version = version + 1; > WHERE id = 1 AND version = current_version; -- 仅当记录中的版本号与当前"版本号"变量一致时, 才进行加锁. > ``` > > <br> ## 悲观锁 「**悲观锁**」:基于**悲观假设**(并发环境中**对共享资源的访问冲突**频繁常见),因此**每次访问共享资源时都必须==直接加锁互斥==**。 数据库提供的 **==具体锁机制==**,例如行锁或者表锁均属于"**悲观锁**" 的一种具体方案。 - 适用场景: - **写操作频繁的场景**:在需要频繁写入数据的场景下,悲观锁可以避免数据不一致和冲突问题。 - **强一致性要求的应用**:如数据库事务中,为了保证数据的准确性,悲观锁会确保资源操作的排他性。 - 优点 - **冲突情况下安全性高**:悲观锁可以保证数据的一致性和安全性,尤其适用于高冲突场景或需要强一致性要求的场景。 - **实现简单**:直接加锁,而**无需复杂的冲突检测和回滚机制** - 缺点: - **性能问题**:在高并发场景下,悲观锁可能导致**锁争用严重**,阻塞其他线程的访问,降低系统的并发性能。 - **死锁风险**:多个线程或进程在获取锁时,如果顺序不当,可能会产生**死锁**。 <br><br><br> # MySQL 锁类型 MySQL 提供**给 DBA 使用的锁**,按 "**粒度**" 分类,包括三种: - (1)**==全局锁==**:对整个数据库加锁,**只允许读**(`SELECT`),而**阻止所有写操作**(包括对表结构和数据的写)。 - 主要用途:创建**数据库备份**(快照)、主从复制 - (2)**==表级锁==**:对 **"整个表"** 加锁; - 主要用途:通常用于 "**DDL**" 语句,例如 `ALTER TABLE` 时,锁住整个表。 - (3)**==行级锁==**:仅对 **"==索引==上的特定==单行记录=="** 加锁,允许其他事务并发访问 "**不同行**"。 - 仅由 InnoDB 引擎提供,仅限于 "**事务**" 内使用。 从 "**功能**" 上,无论是 "**行锁**" 和 "**表锁**",都包括两种具体类型: - **==共享锁== S Lock**(Share Lock):也称 "**读锁**",加锁后其他事务可以再**获取 "共享锁" 而 "==同时读=="**。 - **==排它锁== X Lock**(Exclusive Lock):也称 "**写锁/独占锁**",加锁后其他事务**不能 "读写"**; > [!note] 注: > - **表级锁**:在 MyISAM、InnoDB、MEMORY 引擎中均有提供。 > - **==行级锁==**:仅由 **InnoDB 引擎**提供,仅限在**事务内**使用,InnoDB 底层通过具体的 "记录锁"、"间隙锁"、"临键锁" 来实现。 <br> ## 锁的生命周期 | 锁类型 | | | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | 表锁 | - `LOCK TABLES` 手动加锁后,直到**显式执行 `UNLOCK TABLES` 才会释放**。 <br>- DDL 语句(`ALTER TABLE, DROP TABLE, TRUNCATE TABLE`)会隐式加表锁,**语句结束后自动释放** | | - InnoDB 意向锁 <br>- 元数据锁 MDL <br>- 行锁 | - 非事务环境:**单条语句执行结束后**立即释放<br>- 事务内:同**事务生命周期**,加锁后一直持有,直到**事务提交或回滚** | <br><br> # 上锁命令 ##### 全局锁 ```sql -- 添加全局锁(FTWRL) 注: 会话断开后将自动解锁. FLUSH TABLES WITH READ LOCK; -- 记录 binlog 位置 SHOW MASTER STATUS; -- 备份数据库(使用 mysqldump, 控制台命令) mysqldump -uroot -p123456 数据库名 > 数据库文件名.sql -- 解除全局锁 UNLOCK TABLES; ``` ```sql -- 设置全局只读模式 SET GLOBAL READ_ONLY = 1; -- 取消全局只读模式 SET GLOBAL READ_ONLY = 0; ``` #### 表级锁 ```sql -- 添加表级锁 LOCK TABLES my_table READ; -- 表级共享锁(只读锁, 允许并发读) LOCK TABLES my_table WRITE; -- 表级排它锁(阻塞其他所有读/写) UNLOCK TABLES; -- 解锁; ``` #### 行级锁 - `SELECT ... LOCK IN SHARE MODE`:添加**行级共享锁** - `SELECT ... FOR UPDATE`:添加**行级排它锁** ```sql -- 手动添加行级锁: 为 `id=1` 一行记录添加行级锁 -- 1) 行级排它锁: BEGIN; SELECT * FROM my_table WHERE id = 1 FOR UPDATE; -- 2) 行级共享锁: BEGIN; SELECT * FROM my_table WHERE id = 1 LOCK IN SHARE MODE; -- UPDATE/DELETE会自动 "隐式添加排它锁X" BEGIN; UPDATE my_table SET name = 'new_value' WHERE id = 1; -- InnoDB自动为`id=1`一行记录添加排他锁. DELETE FROM my_table WHERE id = 1; ``` <br><br><br> # MySQL 中对 "表锁" 的支持 MySQL 中在对 "**表锁**" 的底层支持上,引入了三种锁机制: - **==元数据锁==**(Metadata Lock, **==MDL==**):提供对表格元数据的 DLL 保护。 - **元数据读锁**(==MDL-S==) - **元数据写锁**(==MDL-X==) - **==意向锁==**(Intention Lock):用于在 "**事务添加行锁**" 时自动声明意图,提升**表锁性能**。 - **共享意向锁** ==IS==(Itention Shared Lock) - **独占意向锁** ==IX==(Itention Exclusive Lock) - **==自增锁==**(Auto Increment Lock):插入自增列时,加锁以**保证自增 ID 的唯一性**,防止并发插入导致的冲突。 > [!info] 这三种锁皆由 MySQL 底层 "自动加锁/释放" 。 > > - **元数据锁**:由 "**==MySQL 服务器层==**" 提供,各引擎均支持; > - **意向锁**、**自增锁**:仅由 "**==InnoDB 存储引擎==**" 提供。 <br> ## 元数据锁 元数据锁用于 "**数据库对象(表和索引)的元数据**" 提供 DDL 保护。 > [!NOTE] 元数据锁 MDL 是 "**==自动加锁==**",无论是否在事务内。 > > - 在**事务内**,则 **MDL 锁会一直持有到事务结束**(提交 or 回滚) > - 在**非事务环境**,则 **MDL 仅在单条 SQL 语句执行期间持有**。 作用:防止 ==**并发 DDL 冲突**==,以及 **==DDL 与 DML 冲突==("表结构修改" 与 "数据修改" 冲突)**; - 对表进行 **DDL 结构性修改** (`ALTER TABLE` 修改表的元数据 或 `DROP TABLE`)时: - 自动获取 "==**MDL-X 锁**==",**阻止其他事务对该表进行操作(包含==读和改==)**; - 对表进行 **DML、DQL 数据操作**(读**表的元数据** or **读增删改表内数据**) 时: - 自动获取 "**==MDL-S 锁==**",将**阻止其他事务对表进行 "结构性更改"**,防止对表 `ALTER` 或 `DROP`。 ```SQL BEGIN; SELECT * FROM users; -- 自动加 MDL-S 锁 ALTER TABLE users ADD COLUMN age INT; -- 自动加 MDL-X 锁 ``` <br> ## 意向锁 意向锁是 InnoDB 中为 "**==快速判断是否可以获取表锁==**" 而引入的辅助锁,**不阻塞行锁,仅影响 "==表锁==" 的获取**。 > [!NOTE] 作用说明 > > 当 InnoDB 中需要添加 "表锁" 时,必须先判断表中是否 "已有 **==行锁==**",为**避免低效地逐行遍历**而引入了 "**意向锁**"。 > InnoDB 只需要检查是否存在 "**意向锁**",即可判断能否添加 "**表级锁**"。 意向锁包括两种: - **共享意向锁** ==IS==(Itention Shared Lock): - 当事务需要对表中数据行加 "**==行级读锁==**" 时,自动添加 "**==IS 共享意向锁==**"。此后允许获取 "**表级读锁**",但阻止 "**获取表级==写锁==**" - **独占意向锁** ==IX==(Itention Exclusive Lock): - 当事务需要对表中数据行加 "==**行级写锁**==" 时,自动添加 "**==IX 独占意向锁==**"。此后阻止获取任何 **"表级读锁 or 写锁"**。 > [!NOTE] **IS 和 IX 锁之间均 "互不冲突"** > > - 只要事务中 "声明需获取**行级读锁或写锁**",就会同时自动添加相应的 "**==意向锁==**",故一张表**可以同时存在多个 IS 和 IX 锁**。 > > ```SQL > -- 事务 A:对某行加S锁 > BEGIN; > SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE; -- 自动添加IS锁 > > -- 事务 B:对另一行加X锁 > BEGIN; > SELECT * FROM user WHERE id = 2 FOR UPDATE; -- 自动添加IX锁 > ``` > > <br><br><br> # InnoDB 引擎中对 "行锁" 的支持 **InnoDB** 中对 "**行级锁的==实现==**" 以及 "**防止 `REPEATEBLE READ` 级别下的==幻读==**",是通过下列三种具体 "上锁方式" 完成。 | | 作用说明 | | -------------------------- | ----------------------------------------------------------------------------------------------------- | | **记录锁**<br>(Record Lock) | 锁定 "**==索引==**" 上的单个 "**==行记录==**",防止其他事务**修改**该条记录。 | | **间隙锁**<br>(Gap Lock) | 锁定 **"==索引==" 上某个范围(而非具体行)**,防止其他事务**向该范围内插入 or 删除数据**。 <br>- `REPEATABLE READ` 隔离级别下生效,防止 “**幻读**”。 | | **临键锁**<br>(Next-Key Lock) | 等于记录锁 + 间隙锁——同时锁定 "**==某行==**" 及其 "**==前面间隙==**"。可理解为锁住 "`(,]` **左开右闭区间**" | > [!NOTE] 行级锁加在 "**==索引项==**" 上 > > - 走**聚簇索引**时,锁只加在聚簇索引项上。 > - 走**二级索引**时,**二级索引项** 和 **聚簇索引项**都会加锁。 > > [!danger] 如果 where 条件没有使用 "**==索引列==**" 却进行**加锁**,意味着将会 "**全表扫描**",对所有行加锁!相当于全表加锁! > > **带锁 select、update、delete** 均如此。 > 例如 `UPDATE user SET age = 20 WHERE name = "Alice";`, `name` 字段未建立索引,该语句将全表扫描,对所有行加锁![^2] > > 避免办法:可设置参数 `sql_safe_updates = 1`,则 > > - **update** 仅当下列条件之一满足时才执行: > - 使用 `where`,且 `where` 条件中具有索引列; > - 使用 `limit` > - 同时使用 `where` 和 `limit`,此时`where` 中可以无需索引列。 > > - **delete** 仅当下列条件之一满足时才执行: > - 同时使用 `where` 和 `limit`,此时`where` 中可以无需索引列。 > [!note] 对 "相同区间" 的多个 ==**间隙锁**== 可以共存(不区分 X 与 S),其目的只在于 "阻止区间被插入 or 删除区间内记录" > [!info] "**插入间隙锁**" > > 该锁是与 "**间隙锁**" 相关的锁,其作用是 "**标记==等待某个间隙释放锁==**"。 > > 当事务试图**向==被锁定的间隙==插入**时,将生成该锁,多个 "**插入意向锁**" 之间互不冲突。 > 当 "间隙锁" 被释放后,将寻找 "**插入意向锁**" 并唤醒。由于插入意向锁互不冲突,故**可多个事务并发插入**。 <br><br> # InnoDB 行锁的具体加锁形式 上述三种锁是 InnoDB "**==实现行级锁==**" 的机制,而不是提供给 DBA 使用的锁。 在 **不同隔离级别 & 不同场景** 下添加**行锁**时,InnoDB 实际应用的锁形式不同: | 隔离级别 | 行锁的实际类型 | 备注 | | ---- | ----------------------- | ------------ | | 读未提交 | 无锁 | | | 读已提交 | **记录锁** | | | 可重复读 | **临键锁**、**记录锁**、**间隙锁** | 还取决于具体场景,见下文 | | 串行化 | | | > [!example] 三种锁的应用示例 > > 示例一:等值查询 > > ```SQL > -- (1)"读已提交"级别下, InnoDB 将对 id=5 这条记录加"记录锁". > -- (2)"可重复读"级别下: > -- (2.1) 对于"唯一索引or主键索引", 将只对id=5 这条记录加"记录锁". > -- (2.2) 对于"非唯一索引", 将对id=5及其左右相邻间隙均加锁. > -- 例如,`id=5`的相邻值是2与7, 则将加锁(2, 5]+(5, 7). 若id=5记录不存在, 则只加间隙锁(2, 7) > -- 其余事务B将不能插入`id=3,4,6,7`的数据(间隙锁的效果, 防止幻读), 也不能更新`id=5`(记录锁的效果, 防止脏读和不可重复读) > BEGIN; > SELECT * FROM users WHERE id = 5 FOR UPDATE; > UPDATE users set age = 15 WHERE id = 5; -- UPDATE、DELETE语句隐式加行锁, 实际锁形式同上 > ``` > > 示例二:范围查询 > > ```sql > -- (1)"读已提交"级别下, InnoDB 将对 id > 100 的所有记录加 "记录锁". > -- (2)"可重复读"级别下: > -- (2.1) 对于"唯一索引or主键索引": 将只对`id>100`范围上锁. > -- (2.2) 对于"非唯一索引": 将对`id>100`范围, 以及 "id=100的前方间隙" 加锁, > BEGIN; > SELECT * FROM my_table WHERE id > 100 FOR UPDATE; > UPDATE users set age = 15 WHERE id > 100; -- UPDATE、DELETE 语句隐式加行锁, 实际锁形式同上 > ``` > > > <br> ## 「可重复读」级别查询示例 > 详情参见[^1] 该级别下,**行锁**可能具体表现为三种类型:**记录锁、间隙锁、临键锁**,取决于具体场景,如下所示: ![[Excalidraw/Excalidraw-Solution/MySQL 锁.excalidraw|936]] <br> ### 等值查询示例 #### (1)查询值存在 > [!example] 唯一索引—等值查询—值存在 > > ```sql > BEGIN; > SELECT * FROM user WHERE id = 1 FOR UPDATE; > ``` > > > ![[_attachment/02-开发笔记/08-数据库/MySQL 相关/MySQL 锁.assets/IMG-MySQL 锁-D4B9FF9B97438BBD43C56899B5F2F1D8.png|380]] > > [!example] 非唯一索引—等值查询—值存在 > > ```sql > BEGIN; > SELECT * FROM user WHERE age = 22 FOR UPDATE; > ``` > > 值存在,则对**值**本身加**临键锁**,同时对值 "**右侧相邻间隙**" 加**间隙锁**: > > > ![[_attachment/02-开发笔记/08-数据库/MySQL 相关/MySQL 锁.assets/IMG-MySQL 锁-AA3D38BF5B90A3DA0EE6596AF07A7CF8.png|506]] > > #### (2)查询值不存在 > [!example] 唯一索引—等值查询—值不存在 > > ```sql > BEGIN; > SELECT * FROM user WHERE id = 2 FOR UPDATE; > ``` > 值不存在,故对**值所在 "间隙"** 加**间隙锁**: > > ![[_attachment/02-开发笔记/08-数据库/MySQL 相关/MySQL 锁.assets/IMG-MySQL 锁-8D41631E4E4272E6C637E665488CDE4B.png|389]] > > > [!example] 非唯一索引—等值查询—值不存在 > > ```sql > BEGIN; > SELECT * FROM user WHERE age = 25 FOR UPDATE; > ``` > > 值不存在,故对**值所在 "间隙"** 加**间隙锁**: > > ![[_attachment/02-开发笔记/08-数据库/MySQL 相关/MySQL 锁.assets/IMG-MySQL 锁-22729AFE5B15F03A3E44A50CCE186DD3.png|596]] > > 上述间隙锁下,若插入: > > - `age = 22` 的记录:**当 `id < 10` 是可插入的**,若 `id > 10` 则是不可插入的。 > - `age = 39` 的记录:**当 `id > 20` 是可插入的**,若 `id < 20` 则是不可插入的。 > <br> ### 范围查询示例 #### (1)唯一索引上的范围查询 > [!example] 「唯一索引—范围查询——`>` > > ```sql > BEGIN; > SELECT * FROM user WHERE id > 15 FOR UPDATE; > ``` > ![[_attachment/02-开发笔记/08-数据库/MySQL 相关/MySQL 锁.assets/IMG-MySQL 锁-E7D9ED910547D6E0D5EB3F666BCF305B.png|425]] > > > [!example] 「唯一索引—范围查询——`>=` > > ```sql > BEGIN; > SELECT * FROM user WHERE id >= 15 FOR UPDATE; > ``` > > ![[_attachment/02-开发笔记/08-数据库/MySQL 相关/MySQL 锁.assets/IMG-MySQL 锁-AA851F3E77D89541F0B9C1A25B1DDCAF.png|432]] > > [!example] 「唯一索引—范围查询——`<`,值存在 > > ```sql > BEGIN; > SELECT * FROM user WHERE id < 5 FOR UPDATE; > ``` > 注:记录 `id=5` 存在,不取等,故只加 `(1, 5)` 间隙锁 > > ![[_attachment/02-开发笔记/08-数据库/MySQL 相关/MySQL 锁.assets/IMG-MySQL 锁-6959A4349F2E76CD1905C04BEA38BF32.png|391]] > > > [!example] 「唯一索引—范围查询——`<`,值不存在 > > ```sql > BEGIN; > SELECT * FROM user WHERE id < 6 FOR UPDATE; > ``` > > 注意:`id=6` 记录不存在,故**间隙 `(5,10)`** 被加间隙锁。 > > ![[_attachment/02-开发笔记/08-数据库/MySQL 相关/MySQL 锁.assets/IMG-MySQL 锁-4CABB2B4334ABC68FF424378A39D117E.png|386]] > #### (2)非唯一索引上的范围查询 会对条件值的 "**==左右相邻间隙==**" 全部加 "**==临键锁==**"。 > [!example] 「非唯一索引」—范围查询 > > ```sql > BEGIN; > SELECT * FROM user WHERE age >= 22 FOR UPDATE; > ``` > > 注:`(, 22]` 与 `(22, +inf)` 范围**都会被上锁**。 > > ![[_attachment/02-开发笔记/08-数据库/MySQL 相关/MySQL 锁.assets/IMG-MySQL 锁-91B9D1B8D43204820653D230669841E3.png|712]] > > <br><br><br> # 查看加锁情况 通过 `SELECT * FROM performance_schema.data_locks\G;` 可查看事务执行过程中添加的具体锁类型 字段说明: | LOCK_TYPE | LOCK_MODE | LOCK_DATA | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------- | | TABLE(表锁) | - `IX` : 排他意向锁 <br>- `IS`:共享意向锁 | NULL | | RECORD(行锁) | - `X, REC_NOT_GAP`:独占记录锁<br>- `X, GAP`: 独占间隙锁 <br>- `X`: 独占临键锁 <br>- `S, REC_NOT_GAP`:共享记录锁<br>- `S, GAP`: 共享间隙锁 <br>- `S`: 共享临键锁 <br>- `INSERT_INTENTION`:插入意向锁 | 锁的右边界(锁住的索引值) | > [!info] `LOCK_STATUS` 字段为上锁情况,`WATING` 表示在阻塞等待,`GRANTED` 表示当前持有。 > [!example] > > ![[_attachment/02-开发笔记/08-数据库/MySQL 相关/MySQL 锁.assets/IMG-MySQL 锁-7B17875E4DDD3DBC76CDB8E9135C4D70.png|431]] > > > ![[_attachment/02-开发笔记/08-数据库/MySQL 相关/MySQL 锁.assets/IMG-MySQL 锁-67E2A9823C2C89A0EC57C27D0A9E318F.png|472]] > <br><br><br> # 如何避免死锁 数据库层面,有两个办法: - 设置事务**等待锁的超时时间**,**超时回滚**——InnoDB 中,通过参数 `innodb_lock_wait_timeout` 设置,默认为 50 秒。 - 超时信息: ![[_attachment/02-开发笔记/08-数据库/MySQL 相关/MySQL 锁.assets/IMG-MySQL 锁-13068D7ED57A5CF24F654C671631021C.png|710]] - 开启 "**主动死锁检测**"——检测到死锁后,**主动回滚**死锁环路中的某一事务,破坏 "循环等待" 条件。 - 死锁检测信息: ![[_attachment/02-开发笔记/08-数据库/MySQL 相关/MySQL 锁.assets/IMG-MySQL 锁-70AAE4D954A7197FD87B0F3674A1E0C9.png|703]] <br><br><br> # Buffer ## 闪念 > sudden idea ## 候选资料 > Read it later # ♾️参考资料 - [【MySQL】 锁-行级锁、表级锁、全局锁-CSDN博客](https://blog.csdn.net/wy02_/article/details/144596634?spm=1001.2014.3001.5501) - [MySQL 是怎么加锁的? | 小林coding](https://xiaolincoding.com/mysql/lock/how_to_lock.html#%E5%94%AF%E4%B8%80%E7%B4%A2%E5%BC%95%E8%8C%83%E5%9B%B4%E6%9F%A5%E8%AF%A2) - [MySQL 死锁了,怎么办? | 小林coding](https://xiaolincoding.com/mysql/lock/deadlock.html#_1%E3%80%81%E8%AE%B0%E5%BD%95%E4%B9%8B%E9%97%B4%E5%8A%A0%E6%9C%89%E9%97%B4%E9%9A%99%E9%94%81) # Footnotes [^1]: [MySQL 是怎么加锁的? | 小林coding](https://xiaolincoding.com/mysql/lock/how_to_lock.html#%E5%94%AF%E4%B8%80%E7%B4%A2%E5%BC%95%E8%8C%83%E5%9B%B4%E6%9F%A5%E8%AF%A2) [^2]: [Fetching Title#9ouf](https://xiaolincoding.com/mysql/lock/update_index.html#%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BC%9A%E5%8F%91%E7%94%9F%E8%BF%99%E7%A7%8D%E7%9A%84%E4%BA%8B%E6%95%85)