C++/MySQL/Redis 锁机制 - 2
MySQL 锁机制:数据库事务中的数据一致性保障
MySQL 作为关系型数据库,其锁机制与事务隔离级别深度绑定,核心解决多事务并发访问时的数据一致性问题(如脏读、不可重复读、幻读)。锁的粒度从 “表级” 到 “行级”,支持乐观锁与悲观锁,适配不同并发场景。
1. 表级锁:粗粒度悲观锁
核心定义
锁定整个数据表,同一时间仅允许特定类型的操作(读 / 写)执行,是 MySQL 中粒度最粗的锁。MyISAM 存储引擎默认支持,InnoDB 也支持但不常用。
底层实现
读锁(共享锁,S 锁):多个事务可同时获取读锁,允许读操作,禁止写操作;
写锁(排他锁,X 锁):仅一个事务可获取写锁,禁止其他事务读 / 写操作;
锁冲突检测在 MySQL 服务器层完成,无需深入存储引擎,开销低但并发度低。
代码示例(手动加表锁)
1 | -- 1. 会话1:获取表读锁(允许其他会话读,禁止写) |
优缺点与适用场景
维度 | 说明 |
---|---|
优点 | 锁粒度粗,加锁 / 释放开销低;避免行锁的死锁风险。 |
缺点 | 并发度极低,写操作会阻塞所有读 / 写;不适用于高并发写场景。 |
适用场景 | MyISAM 存储引擎(已逐步淘汰);批量数据导入 / 导出(一次性操作全表);低并发的小表。 |
与 C++ 对比 | 类似 C++ 的 std::mutex(粗粒度互斥),但 MySQL 表锁基于 “表” 维度,C++ 锁基于 “内存资源” 维度。 |
2. 行级锁:细粒度悲观锁(InnoDB 核心)
核心定义
InnoDB 存储引擎的核心锁机制,仅锁定数据表中被操作的行记录,而非整个表。支持 “共享锁(S 锁)” 和 “排他锁(X 锁)”,并发度远高于表级锁,是高并发 MySQL 场景的首选。
底层实现
基于 “聚簇索引”(主键索引)实现:锁定行记录时,实际锁定索引树中的对应节点;
支持 “间隙锁(Gap Lock)” 和 “临键锁(Next-Key Lock)”,防止幻读(默认 RR 隔离级别下);
锁冲突检测在 InnoDB 存储引擎层完成,粒度细但开销高于表级锁。
代码示例(行锁使用场景)
1 | -- 前提:InnoDB 存储引擎,表 user_info 有主键 id |
优缺点与适用场景
维度 | 说明 |
---|---|
优点 | 锁粒度细,并发度高;支持事务 ACID 特性;防止脏读、不可重复读、幻读。 |
缺点 | 加锁 / 释放开销高;可能因锁竞争导致死锁(需通过 SHOW ENGINE INNODB STATUS 排查);依赖主键索引(无主键时退化为表锁)。 |
适用场景 | 高并发写场景(如电商订单、用户余额更新);InnoDB 存储引擎的核心业务表。 |
与 Java 对比 | 类似 Java 的 “分段锁”(如 ConcurrentHashMap),均通过 “细粒度锁定” 提升并发度,但 MySQL 行锁基于 “数据行”,Java 分段锁基于 “哈希段”。 |
3. 意向锁:表级锁与行级锁的桥梁
核心定义
InnoDB 为解决 “表级锁与行级锁冲突检测” 引入的中间锁,分为 “意向共享锁(IS 锁)” 和 “意向排他锁(IX 锁)”。事务获取行级锁前,会先自动获取对应的意向锁,无需手动操作。
底层实现
意向共享锁(IS):事务计划获取某行的 S 锁前,先获取表的 IS 锁;
意向排他锁(IX):事务计划获取某行的 X 锁前,先获取表的 IX 锁;
意向锁不阻塞读 / 写操作,仅用于快速检测表级锁与行级锁的冲突(如避免 “表写锁” 与 “行读锁” 共存)。
冲突规则表
锁类型 | 读锁(S) | 写锁(X) | 意向读锁(IS) | 意向写锁(IX) |
---|---|---|---|---|
读锁(S) | 兼容 | 冲突 | 兼容 | 兼容 |
写锁(X) | 冲突 | 冲突 | 冲突 | 冲突 |
意向读锁(IS) | 兼容 | 冲突 | 兼容 | 兼容 |
意向写锁(IX) | 兼容 | 冲突 | 兼容 | 兼容 |
适用场景
透明存在于 InnoDB 事务中,无需手动管理;
主要用于 “表级锁与行级锁共存” 的场景(如某事务加表读锁,另一事务加行写锁时,通过意向锁快速检测冲突)。
4. 乐观锁:基于版本号 / 时间戳的无锁机制
核心定义
MySQL 不提供原生乐观锁,需通过业务逻辑实现:基于 “版本号(version)” 或 “时间戳(update_time)” 字段,事务操作时不预先加锁,而是在更新时检查数据是否被修改,若未修改则更新,否则重试 / 失败。
底层实现
表结构添加版本字段:ALTER TABLE user_info ADD COLUMN version INT DEFAULT 1;;
事务读取数据时,同时读取版本号:SELECT id, name, version FROM user_info WHERE id = 1;;
更新时检查版本号:UPDATE user_info SET name = 'Charlie', version = version + 1 WHERE id = 1 AND version = 1;;
通过 ROW_COUNT() 检查更新行数,若为 0 表示数据已被修改,需重试。
代码示例(版本号实现乐观锁)
1 | -- 1. 会话1:读取数据与版本号 |
优缺点与适用场景
维度 | 说明 |
---|---|
优点 | 无锁机制,并发度高;避免行锁的死锁与阻塞问题;实现灵活。 |
缺点 | 需手动维护版本号字段;高竞争场景下重试次数多,影响性能;不支持跨表操作。 |
适用场景 | 读多写少场景(如用户资料修改、商品库存查询);低并发更新的业务表。 |
与 Redis 对比 | 类似 Redis 的 “分布式乐观锁”,但 MySQL 乐观锁基于 “表字段”,Redis 基于 “键值对”,且 Redis 支持原子操作(如 SETNX)。 |