最近正在复习Java八股,所以会将一些热门的八股问题,结合ai与自身理解写成博客便于记忆
一、锁的基本概念与分类
1. 按锁粒度划分
锁类型 | 描述 | 开销 | 并发度 | 适用场景 |
全局锁 | 锁定整个数据库实例 | 大 | 低 | 全库逻辑备份 |
表级锁 | 锁定整张表 | 中 | 中 | 数据迁移、DDL操作 |
行级锁 | 锁定单行或多行记录 | 小 | 高 | 高并发事务场景 |
2. 按锁性质划分
锁类型 | 描述 | 典型场景 |
共享锁(S锁) | 允许多个事务同时读取 | SELECT ... LOCK IN SHARE MODE |
排他锁(X锁) | 独占资源,阻止其他任何锁 | SELECT ... FOR UPDATE |
意向共享锁(IS) | 表明事务打算在表中设置共享锁 | 自动添加,无需手动操作 |
意向排他锁(IX) | 表明事务打算在表中设置排他锁 | 自动添加,无需手动操作 |
二、InnoDB行锁实现原理
1. 记录锁(Record Lock)
-- 锁定单行记录(id=1)
SELECT * FROM users WHERE id = 1 FOR UPDATE;
实现机制:
在索引记录上加锁
若查询无索引会升级为表锁
2. 间隙锁(Gap Lock)
-- 锁定id在(5,10)区间的间隙
SELECT * FROM users WHERE id BETWEEN 5 AND 10 FOR UPDATE;
特性:
防止幻读
只在可重复读隔离级别生效
3. 临键锁(Next-Key Lock)
-- 锁定id<=10的所有记录及间隙
SELECT * FROM users WHERE id <= 10 FOR UPDATE;
组成:
记录锁 + 间隙锁
InnoDB默认行锁算法
4. 插入意向锁(Insert Intention Lock)
作用:
提高并发插入性能
不同事务在相同间隙插入不冲突
三、锁的监控与诊断
1. 查看锁状态
-- 查看当前锁等待
SELECT * FROM performance_schema.events_waits_current
WHERE EVENT_NAME LIKE '%lock%';
-- 查看InnoDB锁信息(MySQL 8.0+)
SELECT * FROM performance_schema.data_locks;
-- 查看锁等待关系
SELECT * FROM sys.innodb_lock_waits;
2. 锁等待超时参数
-- 锁等待超时时间(默认50秒)
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';
-- 死锁检测开关(默认ON)
SHOW VARIABLES LIKE 'innodb_deadlock_detect';
四、常见面试问题解析
1:InnoDB什么情况下会升级为表锁?
参考答案:
1. 查询条件无可用索引(全表扫描)
2. 事务涉及大量行(超过阈值innodb_table_locks)
3. 执行ALTER TABLE等DDL操作时
4. 显式请求表锁(LOCK TABLES命令)
2:如何解决死锁问题?
解决方案:
1. 设置合理的超时时间:innodb_lock_wait_timeout
2. 启用死锁检测:innodb_deadlock_detect=ON
3. 保证资源访问顺序一致
4. 减小事务粒度
5. 使用SHOW ENGINE INNODB STATUS分析死锁日志
五、MVCC核心概念
1. MVCC定义
多版本并发控制(Multi-Version Concurrency Control)是InnoDB实现高并发的重要机制,通过在同一时刻保存数据多个版本,实现:
读操作不阻塞写操作
写操作不阻塞读操作
解决幻读问题(在RR隔离级别)
2. 与锁机制的关系
机制 | 解决的核心问题 | 实现方式 | 性能影响 |
锁机制 | 数据修改冲突 | 阻塞等待 | 高(并发度低) |
MVCC | 读写冲突 | 多版本访问 | 低(并发度高) |
六、MVCC实现核心要素
1. ReadView机制
class ReadView {
long m_low_limit_id; // 高水位:大于等于此ID的事务均不可见
long m_up_limit_id; // 低水位:小于此ID的事务均可见
long m_creator_trx_id; // 创建该ReadView的事务ID
Set<Long> m_ids; // 活跃事务列表
bool is_visible(long trx_id) {
if (trx_id == m_creator_trx_id) return true;
if (trx_id < m_up_limit_id) return true;
if (trx_id >= m_low_limit_id) return false;
return !m_ids.contains(trx_id);
}
}
七、不同隔离级别的MVCC实现
1.READ-UNCOMMITTED(读取未提交)
不适用MVCC:直接读取最新数据(可能脏读)
实现方式:无ReadView,总是读取最新版本
2. READ-COMMITTED(读取已提交)
每次读取生成新ReadView
可见性规则:
-- 事务A(ID=100)执行:
BEGIN;
SELECT * FROM t; -- 此时生成ReadView1
-- 事务B(ID=200)提交更新后
SELECT * FROM t; -- 重新生成ReadView2,看到事务B的修改
3. REPEATABLE READ(可重复读)(InnoDB默认)
首次读取时生成ReadView,整个事务期间复用
可见性规则:
-- 事务A(ID=100)执行:
BEGIN;
SELECT * FROM t; -- 生成ReadView
-- 事务B(ID=200)提交更新后
SELECT * FROM t; -- 仍使用之前的ReadView,看不到事务B的修改
4. SERIALIZABLE(可串行化)
退化为纯锁机制:所有SELECT自动转为SELECT...LOCK IN SHARE MODE
MVCC失效:通过锁保证串行化
八、MVCC与锁的协同工作
1. 写操作流程
UPDATE account SET balance = balance - 100 WHERE id = 1;
1. 获取X锁锁定记录
2. 将当前记录写入undo log
3. 修改记录并更新DB_TRX_ID
4. 将回滚指针指向undo log
2. 读操作流程
SELECT * FROM account WHERE id = 1;
1. 检查记录上的X锁(如有则等待)
2. 根据ReadView判断可见性
3. 沿undo log版本链查找可见版本
九、MVCC解决幻读的机制
1. 快照读(Snapshot Read)
-- 普通SELECT使用MVCC(无锁)
SELECT * FROM users WHERE age > 20;
实现方式:
基于ReadView判断可见性
通过版本链访问历史数据
2. 当前读(Current Read)
-- 加锁SELECT使用记录锁+间隙锁
SELECT * FROM users WHERE age > 20 FOR UPDATE;
实现方式:
对符合条件的记录加X锁
对查询范围加间隙锁(防止其他事务插入)
十、面试高频问题
1:MVCC如何实现RR级别防幻读?
参考答案:
1. 快照读:通过首次查询时生成的ReadView保证整个事务看到一致的数据快照
2. 当前读:通过Next-Key Lock(记录锁+间隙锁)防止其他事务插入新记录
3. undo版本链:确保可以访问事务开始时的数据版本
2:MVCC能否完全避免加锁?
答案分析:
读操作:可以完全无锁(快照读)
写操作:必须加锁保证原子性
特殊情况:
-- 混合操作仍需加锁
BEGIN;
SELECT * FROM users WHERE id = 1; -- 无锁(MVCC)
UPDATE users SET name = 'a' WHERE id=1; -- 需要X锁