湘西土家族苗族自治州网站建设_网站建设公司_轮播图_seo优化
2025/12/17 23:39:03 网站建设 项目流程

MySQL 的锁机制是实现并发控制的核心,不同存储引擎和隔离级别下的锁策略差异巨大。以下按锁粒度 → 锁模式 → 特殊锁 → 死锁处理的顺序系统讲解

一、按粒度分类:表锁 vs 行锁

锁的粒度决定了锁的并发度开销,是锁机制的首要维度

1. 表锁(Table Lock)

对整个表加锁,开销小、加锁快,但并发度最低。
MyISAM 存储引擎

-- 自动加表锁:读锁(共享锁)SELECT*FROMusersWHEREid=1;-- 自动加 READ LOCK-- 自动加表锁:写锁(排他锁)UPDATEusersSETname='张三'WHEREid=1;-- 自动加 WRITE LOCK

锁机制:

  • 读锁(READ LOCK):允许其他会话读,禁止写
  • 写锁(WRITE LOCK):禁止其他会话读和写
  • 锁排队:读锁和写锁互斥,串行执行

适用场景:读多写少的静态表(如配置表),不适合高并发写入场景。

InnoDB 存储引擎的表锁

-- 手动加表级 READ LOCKLOCKTABLESusersREAD;-- 手动加表级 WRITE LOCKLOCKTABLESusersWRITE;-- 查看表锁状态SHOWOPENTABLESWHEREIn_use>0;

⚠️ 注意:InnoDB 默认使用行锁,表锁仅用于特殊场景(如 DDL 操作),手动加表锁会降级并发性能

2. 行锁(Row Lock)

对索引记录加锁,开销大、加锁慢,但并发度最高

-- InnoDB 自动加行锁(RC/RR 隔离级别)UPDATEusersSETname='张三'WHEREid=1;-- 对 id=1 的行加排他锁

行锁的 3 种实现方式

行锁类型锁定范围示例说明
Record Lock精确索引记录WHERE id = 1锁定主键值为 1 的索引记录
Gap Lock索引间隙(不含记录)WHERE id BETWEEN 10 AND 20(RR 级别)防止幻读,锁定 10-20 的开区间
Next-Key Lock记录 + 间隙WHERE id >= 10(RR 级别)Gap Lock + Record Lock,锁定 (负无穷, 10]

⚠️关键:行锁必须命中索引,否则退化为表锁

-- ❌ 行锁失效:name 无索引,退化为表锁UPDATEusersSETage=20WHEREname='张三';-- 锁全表!-- ✅ 行锁生效:name 有索引ALTERTABLEusersADDINDEXidx_name(name);UPDATEusersSETage=20WHEREname='张三';-- 仅锁匹配行

二、按模式分类:共享锁、排他锁、意向锁

1. 共享锁(S Lock,Shared Lock)

允许其他事务读,禁止写

-- 显式加共享锁(FOR SHARE 是 MySQL 8.0+ 语法,兼容 READ LOCK)SELECT*FROMusersWHEREid=1FORSHARE;-- MySQL 8.0+SELECT*FROMusersWHEREid=1LOCKINSHAREMODE;-- MySQL 5.x-- 效果:其他事务可读,但无法获取排他锁(无法 UPDATE/DELETE)

应用场景:读取数据后需保证不被修改(如财务对账)

2. 排他锁(X Lock,Exclusive Lock)

禁止其他事务读和写

-- 自动加排他锁UPDATEusersSETname='张三'WHEREid=1;-- 显式加排他锁SELECT*FROMusersWHEREid=1FORUPDATE;

关键行为

  • UPDATE/DELETE:自动加 X 锁
  • SELECT … FOR UPDATE:显式加 X 锁,用于悲观锁场景(如秒杀扣库存)
  • 锁升级:同一事务可对同一行先加 S 锁,再加 X 锁

3. 意向锁(Intention Lock)

表级锁,表明事务稍后要对表中的行加锁,是 InnoDB 实现表锁和行锁共存的关键

-- 事务 1:对 users 表某行加 X 锁(自动加意向排他锁 IX)BEGIN;UPDATEusersSETname='张三'WHEREid=1;-- 自动在 users 表加 IX 锁-- 事务 2:尝试对 users 表加表锁LOCKTABLESusersREAD;-- 被阻塞!因为检测到 IX 锁
意向锁类型说明兼容性
IS(意向共享锁)事务想对表中的行加 S 锁与表级 READ 锁兼容
IX(意向排他锁)事务想对表中的行加 X 锁与任何表锁都冲突

意义:避免表锁和行锁冲突导致的死锁,提升检查效率

三、InnoDB 特殊锁机制

1. 间隙锁(Gap Lock)

RR(可重复读)隔离级别下,防止幻读的关键机制

-- 假设 users 表中 id 为 1, 5, 10, 20-- 事务 1(RR 级别)BEGIN;SELECT*FROMusersWHEREidBETWEEN5AND15FORUPDATE;-- 锁定范围:(5, 10) 和 (10, 15) 这两个间隙-- 关键字:锁定开区间 (5, 15),不包含 5 和 15-- 事务 2(被阻塞)INSERTINTOusers(id,name)VALUES(8,'李四');-- 被阻塞!间隙被锁INSERTINTOusers(id,name)VALUES(3,'王五');-- 成功,不在锁定间隙

目的:防止其他事务在范围内插入新记录,避免"幻读"

2. 临键锁(Next-Key Lock)

Gap Lock + Record Lock,锁定左开右闭区间(prev_record, record]

-- 事务 1(RR 级别)BEGIN;SELECT*FROMusersWHEREid>=10FORUPDATE;-- 锁定范围:(负无穷, 1], (1, 5], (5, 10], (10, 正无穷)-- 关键点:包含记录本身及其左侧的间隙-- 事务 2INSERTINTOusers(id,name)VALUES(9,'赵六');-- 被阻塞(锁定 (5,10] 间隙)INSERTINTOusers(id,name)VALUES(11,'孙七');-- 被阻塞(锁定 (10, 正无穷))

InnoDB 默认锁:在 RR 级别,普通索引加 Next-Key Lock,主键索引加 Record Lock。

3. 插入意向锁(Insert Intention Lock)

一种特殊的间隙锁,表明事务想在某个间隙插入记录,多个事务可同时在不同位置插入(提高并发)

-- 事务 1BEGIN;SELECT*FROMusersWHEREid=5FORUPDATE;-- 对 id=5 加 X 锁-- 事务 2BEGIN;SELECT*FROMusersWHEREid=5FORUPDATE;-- 被阻塞,等待事务 1 释放-- 事务 3(可成功)INSERTINTOusers(id,name)VALUES(6,'周八');-- 成功!只等待 id=5 的锁

机制:插入时先加 Insert Intention Lock,检查间隙是否冲突,不冲突则等待 Gap Lock 释放后插入

4. 自增锁(AUTO-INC Lock)

表级锁,用于保证自增列值的唯一性

CREATETABLEusers(idBIGINTAUTO_INCREMENTPRIMARYKEY,nameVARCHAR(50));-- 插入时自动加自增锁INSERTINTOusers(name)VALUES('张三'),('李四'),('王五');-- 锁定自增计数器,保证三个 ID 连续且唯一

锁模式(innodb_autoinc_lock_mode):

  • 0(Traditional):每次插入都加表级锁,性能最差,保证连续
  • 1(Consecutive):批量插入加表级锁,简单插入用轻量锁(默认)
  • 2(Interleaved):无锁,性能最好,但 ID 可能不连续(适合高并发)

四、不同隔离级别下的锁行为

RC(读已提交)

  • 行锁:仅对匹配行加 Record Lock
  • 无 Gap Lock:不锁定间隙,允许插入,可能产生幻读
  • 一致性:每次 SELECT 生成新 Read View,可能读到其他事务已提交数据
-- 事务 1(RC 级别)BEGIN;SELECT*FROMusersWHEREid=10FORUPDATE;-- 仅锁定 id=10 的行-- 事务 2INSERTINTOusers(id,name)VALUES(9,'吴九');-- ✅ 成功,间隙未锁

RR(可重复读)

  • 行锁:对匹配行和间隙加 Next-Key Lock
  • Gap Lock:锁定范围,防止幻读
  • 一致性:事务启动时生成 Read View,保证可重复读
-- 事务 1(RR 级别)BEGIN;SELECT*FROMusersWHEREidBETWEEN5AND15FORUPDATE;-- 锁定 (5,15] 间隙-- 事务 2INSERTINTOusers(id,name)VALUES(8,'郑十');-- ❌ 被阻塞

Serializable

  • 读加锁:普通 SELECT 自动加S 锁
  • 完全串行:所有操作串行执行,并发度最低,数据一致性最强

五、死锁(Deadlock)与排查

死锁产生条件

  • 互斥条件:资源不能共享
  • 请求与保持:持有资源同时申请新资源
  • 不可剥夺:资源只能主动释放
  • 循环等待:形成等待环路

经典死锁案例

-- 事务 1BEGIN;UPDATEusersSETname='A'WHEREid=1;-- 持有 id=1 的 X 锁-- 等待 id=2 的 X 锁UPDATEusersSETname='B'WHEREid=2;-- 事务 2(同时执行)BEGIN;UPDATEusersSETname='C'WHEREid=2;-- 持有 id=2 的 X 锁-- 等待 id=1 的 X 锁,形成循环等待UPDATEusersSETname='D'WHEREid=1;-- Deadlock!

InnoDB 处理:自动检测死锁,回滚持有锁最少的事务

死锁排查命令

-- 查看最近一次死锁信息SHOWENGINEINNODBSTATUS\G-- 输出:-- -------------------------- LATEST DETECTED DEADLOCK-- -------------------------- *** (1) TRANSACTION:-- UPDATE users SET name = 'B' WHERE id = 2-- *** (2) TRANSACTION:-- UPDATE users SET name = 'D' WHERE id = 1-- *** WE ROLL BACK TRANSACTION (2) -- 回滚事务 2-- 查看当前锁等待SELECT*FROMinformation_schema.INNODB_LOCK_WAITS;-- 查看当前事务SELECT*FROMinformation_schema.INNODB_TRX;-- 查看当前锁SELECT*FROMinformation_schema.INNODB_LOCKS;

六、锁优化最佳实践

1. 索引设计优化

-- ❌ 导致表锁UPDATEusersSETname='张三'WHEREname='李四';-- name 无索引-- ✅ 保证行锁ALTERTABLEusersADDINDEXidx_name(name);UPDATEusersSETname='张三'WHEREname='李四';-- name 有索引

2. 事务粒度控制

-- ❌ 长事务持有锁时间过长BEGIN;-- 处理 10 秒业务逻辑UPDATEusersSETscore=score+10WHEREid=1;COMMIT;-- ✅ 缩短事务,减少锁持有时间-- 业务逻辑提前处理BEGIN;UPDATEusersSETscore=score+10WHEREid=1;COMMIT;

3. 避免热点行

-- ❌ 高并发下所有请求锁同一行UPDATEinventorySETstock=stock-1WHEREproduct_id=1;-- ✅ 拆分到多行,减少冲突UPDATEinventorySETstock=stock-1WHEREproduct_id=1ANDwarehouse_id=MOD(NOW(),10);

4. 死锁预防

  • 固定顺序访问:所有事务按相同顺序申请锁(如先锁 id=1,再锁 id=2)
  • 降低隔离级别:将 RR 改为 RC,减少 Gap Lock
  • 批量操作拆分:将大事务拆分为小事务,减少锁持有时间

5.监控锁等待

-- 查看锁等待超时时间SHOWVARIABLESLIKE'innodb_lock_wait_timeout';-- 默认 50 秒-- 设置会话级超时时间SETSESSIONinnodb_lock_wait_timeout=10;-- 10 秒超时-- 开启死锁日志SETGLOBALinnodb_print_all_deadlocks=ON;

七、总结

锁类型粒度模式适用场景性能影响
表锁S/XMyISAM 读多写少表并发度低
行锁S/XInnoDB 高并发 OLTP并发度高
意向锁IS/IXInnoDB 表锁检查几乎无影响
间隙锁行间隙GapRR 级别防幻读可能阻塞插入
临键锁行+间隙Next-KeyRR 级别默认锁最严格
自增锁AUTO-INC自增列赋值批量插入影响性能

核心原则:

  • 优先行锁:InnoDB 行锁并发度远高于表锁
  • 命中索引:确保 WHERE 条件走索引,避免退化为表锁
  • 缩短事务:减少锁持有时间,降低死锁概率
  • 监控死锁:定期分析死锁日志,优化事务顺序

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询