MySQL 的锁机制【深度全面】

发布于:2025-06-06 ⋅ 阅读:(19) ⋅ 点赞:(0)

一、锁的核心作用

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

  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 的锁机制!


网站公告

今日签到

点亮在社区的每一天
去签到