当前位置: 首页 > news >正文

MySQL 的锁机制【深度全面】

一、锁的核心作用

锁的本质:就像图书馆的借阅规则,避免多个人同时修改一本书导致混乱。
核心目标

  1. 数据一致性:确保多个事务操作数据时不会出现矛盾。
  2. 并发控制:允许事务同时操作不同数据,但禁止冲突操作。

二、锁的分类(按粒度)

1. 全局锁
  • 命令FLUSH TABLES WITH READ LOCK;
  • 特点
    • 整个数据库变成“只读”,所有写操作(增删改)被阻塞。
    • 用例:全库备份(比如 mysqldump)。
  • 风险:如果在主库上执行,业务会瘫痪!

实例

-- 假设你在备份数据库时执行全局锁
FLUSH TABLES WITH READ LOCK;
-- 此时其他事务尝试执行以下操作会阻塞:
UPDATE users SET balance = 100 WHERE id = 1; -- 被阻塞

2. 表级锁
(1)表锁
  • 类型
    • 读锁(共享锁):允许多人同时读,但禁止写。
    • 写锁(排他锁):禁止其他人读或写。
  • 命令
    LOCK TABLES users READ; -- 读锁
    LOCK TABLES users WRITE; -- 写锁
    UNLOCK TABLES; -- 释放锁
    

实例

-- 客户端A加读锁
LOCK TABLES users READ;
SELECT * FROM users; -- 成功
-- 客户端B尝试写操作
UPDATE users SET name = 'Tom' WHERE id = 1; -- 被阻塞
(2)元数据锁(MDL)
  • 特点
    • 自动加锁(无需手动操作)。
    • 保护表结构(如字段、索引)。
  • 触发条件
    • DML(如 SELECT)自动加 MDL 读锁
    • DDL(如 ALTER TABLE)自动加 MDL 写锁

实例

-- 客户端A正在查询表
SELECT * FROM users; -- 自动加 MDL 读锁
-- 客户端B尝试修改表结构
ALTER TABLE users ADD COLUMN age INT; -- 被阻塞
(3)意向锁
  • 作用:告诉其他事务“我打算锁某几行,别直接锁整张表”。
  • 类型
    • 意向共享锁(IS):事务打算对某些行加共享锁。
    • 意向排他锁(IX):事务打算对某些行加排他锁。

实例

-- 事务A对某行加排他锁
START TRANSACTION;
SELECT * FROM users WHERE id = 1 FOR UPDATE; -- 自动加 IX 意向锁
-- 事务B尝试加表锁
LOCK TABLES users WRITE; -- 被阻塞(因为事务A持有 IX 锁)

3. 行级锁
  • 引擎:InnoDB 支持行级锁。
(1)记录锁(Record Lock)
  • 场景:某一行被锁定,其他人不能修改。
  • 用例:等值查询命中唯一索引。

实例

-- 事务A锁定 id=1 的行
START TRANSACTION;
SELECT * FROM users WHERE id = 1 FOR UPDATE; -- 加排他锁
-- 事务B尝试修改同一行
UPDATE users SET name = 'Tom' WHERE id = 1; -- 被阻塞
(2)间隙锁(Gap Lock)
  • 目的:防止幻读(Phantom Read)。

实例

-- 假设 users 表有 id=10, 20, 30
-- 事务A锁定 id 在 10~20 之间的间隙
START TRANSACTION;
SELECT * FROM users WHERE id BETWEEN 10 AND 20 FOR UPDATE; -- 加间隙锁
-- 事务B尝试插入 id=15
INSERT INTO users (id, name) VALUES (15, 'Alice'); -- 被阻塞
(3)临键锁(Next-Key Lock)
  • 组成:记录锁 + 间隙锁。

实例

-- 假设 users 表有 id=10, 20, 30
-- 事务A锁定 id=20 的记录及其右边的间隙(20~30)
START TRANSACTION;
SELECT * FROM users WHERE id = 20 FOR UPDATE; -- 加临键锁
-- 事务B尝试插入 id=25
INSERT INTO users (id, name) VALUES (25, 'Bob'); -- 被阻塞
(4)插入意向锁(Insert Intention Lock)
  • 场景:图书馆中,多个读者想在同一个书架的不同位置插入新书,互不干扰。
  • 特点:插入时加锁,但与其他插入意向锁兼容。

实例

-- 假设 users 表有 id=10, 30
-- 事务A和事务B同时尝试插入 id=20 和 id=25
START TRANSACTION;
INSERT INTO users (id, name) VALUES (20, 'Alice'); -- 加插入意向锁
-- 事务B插入 id=25 不会被阻塞
INSERT INTO users (id, name) VALUES (25, 'Bob'); -- 成功

三、锁的类型(按互斥性)

1. 共享锁(Shared Lock,S锁)
  • 特点
    • 多事务可以同时读,但不能写。
    • LOCK IN SHARE MODE 显式加锁。

实例

-- 事务A加共享锁
START TRANSACTION;
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE; -- 允许其他事务读
-- 事务B尝试修改
UPDATE users SET name = 'Tom' WHERE id = 1; -- 被阻塞
2. 排他锁(Exclusive Lock,X锁)
  • 特点
    • 只能有一个事务持有。
    • FOR UPDATE 显式加锁。

实例

-- 事务A加排他锁
START TRANSACTION;
SELECT * FROM users WHERE id = 1 FOR UPDATE; -- 独占该行
-- 事务B尝试读或写
SELECT * FROM users WHERE id = 1; -- 被阻塞(如果事务A未提交)

四、乐观锁 vs 悲观锁

1. 乐观锁(Optimistic Locking)
  • 实现:通过版本号(version 字段)或时间戳(timestamp)。

实例

-- 更新前检查版本号
UPDATE users 
SET balance = balance - 100, version = version + 1 
WHERE id = 1 AND version = 5;
-- 如果返回影响行数为0,说明版本冲突
2. 悲观锁(Pessimistic Locking)
  • 实现:通过 SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODE

实例

-- 事务A加锁并更新
START TRANSACTION;
SELECT * FROM users WHERE id = 1 FOR UPDATE; -- 加排他锁
UPDATE users SET balance = balance - 100 WHERE id = 1;
COMMIT;
-- 事务B必须等待事务A提交后才能操作

五、锁的兼容性表

当前锁 \ 请求锁S锁X锁IS锁IX锁
S锁✔️✖️✔️✔️
X锁✖️✖️✖️✖️
IS锁✔️✖️✔️✔️
IX锁✔️✖️✔️✔️

六、锁的最佳实践

  1. 选择合适的锁粒度

    • 高并发场景用行锁(InnoDB)。
    • 批量操作或低并发场景用表锁(MyISAM)。
  2. 避免死锁

    • 按固定顺序加锁(如先更新表A再更新表B)。
    • 减少事务持有锁的时间。
  3. 合理使用隔离级别

    • RR(可重复读)下默认使用 Next-Key Lock 防止幻读。
    • RC(读已提交)下仅使用记录锁。
  4. 监控锁状态

    • 通过 SHOW ENGINE INNODB STATUS 查看死锁信息。
    • 使用 performance_schema.data_locks 分析锁冲突。

七、总结

  • 全局锁:只读整个数据库,慎用!
  • 表锁:MyISAM 默认,InnoDB 支持但优先行锁。
  • 行锁:InnoDB 的核心,分为记录锁、间隙锁、临键锁。
  • 乐观锁:适合冲突少的场景,通过版本号实现。
  • 悲观锁:适合冲突多的场景,显式加锁。

通过合理选择锁类型和隔离级别,可以高效解决并发问题,避免数据不一致。希望这些例子和解释能帮助你更好地理解 MySQL 的锁机制!

http://www.lqws.cn/news/159067.html

相关文章:

  • HBuilder 发行Android(apk包)全流程指南
  • Flyway
  • Spring WebFlux 整合AI大模型实现流式输出
  • 数据库优化实战分享:高频场景下的性能调优技巧与案例解析
  • c#基础010(程序结构)
  • 深度解析数字营销专属大模型 AdLLM 的训练思路
  • 监控硬盘可以当台式机硬盘用吗
  • 【数据结构】5. 双向链表
  • Vue3解决“找不到模块@/components/xxx.vue或其相应的类型声明ts文件(2307)”
  • BLOB 是用来存“二进制大文件”的字段类型
  • GO协程(Goroutine)问题总结(待续)
  • 自建 Derp 中继节点
  • [蓝桥杯]航班时间
  • RK3588 InsightFace人脸识别移植及精度测试全解析
  • UE Learning Record
  • 在嵌入式中C语言中static修饰的变量常量和字符串常量存储位置
  • EFI(x64)简易开发环境
  • 优化Docker容器化安装与配置的最佳实践
  • 将图形可视化工具的 Python 脚本打包为 Windows 应用程序
  • Java线程安全集合类
  • 贪心,回溯,动态规划
  • HTV 3.3 | 秒播无卡顿 直播源每天维护更新
  • 【定昌linux开发板】关闭ssh 端口 22
  • Rocketmq消息队列 消息模型 详解
  • 虚拟机网络配置
  • css实现文字颜色渐变
  • 深入理解汇编语言子程序设计与系统调用
  • 第十三节:第四部分:集合框架:HashMap、LinkedHashMap、TreeMap
  • MCP通信方式之Streamable HTTP
  • 开始在本地部署自己的 Gitea 服务器