Mysql中的锁到底是什么?锁的是什么?

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

MySQL InnoDB 的锁:一次从“守卫”到“交通指挥中心”的深度之旅

MySQL InnoDB 的锁。这个概念常常让人觉得复杂抽象,但我们需要抓住它的底层设计哲学

忘记那些代码和术语定义,我们先从最底层的问题开始思考:

思考一:为什么我们需要锁?

想象一下,你和你的朋友同时去取款机取钱。你取100,他取200。如果没锁,可能发生什么?

  • 你看到余额1000,他同时看到余额1000。
  • 你取100,系统计算:1000 - 100 = 900,还没写入。
  • 他取200,系统计算:1000 - 200 = 800,还没写入。
  • 你写入900。
  • 他写入800。

最终余额变成了800,而不是你预期的700。这就是典型的并发问题——数据不一致

锁,就是为了解决这个“并发冲突”而生的“守卫”。它确保在某个时刻,对某个资源的操作是“有秩序”的,不会出现混乱。

思考二:这个“守卫”长什么样?它在哪里站岗?

如果你接触过 Java 的 synchronized 关键字,你可能会觉得,锁就是每个对象上“自带”的一把小锁。对象被访问时,自己检查自己的锁状态。

但数据库的锁远非如此简单!

数据库要处理的数据量是海量的,并发事务可能是成千上万的。如果每个数据行、每个数据页都自带一把锁,会有以下问题:

  1. 管理噩梦:成千上万的小锁,怎么知道哪些被占用?哪些在等待?
  2. 效率低下:每次访问数据都要去数据本身那里检查锁状态,频繁的磁盘I/O(数据在磁盘上)。
  3. 死锁侦探难上加难:无法构建全局视图,发现复杂的循环等待(死锁)。

所以,InnoDB 采取了一种截然不同的、更高级的、更具“智慧”的设计:中心化的锁管理

数据库的“守卫”,不是分散在每个数据旁边的个体户,而是一个独立的、高智能的“中央交通指挥中心”——我们称之为 Lock Manager。

这个 Lock Manager 驻扎在内存中,它不直接触摸磁盘上的数据,而是:

  • 观察:所有事务对数据的请求,都会先汇报给 Lock Manager。
  • 记录:Lock Manager 维护着一张全球地图级别的“交通登记簿”。
  • 协调:它根据规则,决定放行哪个请求,暂停哪个请求。
  • 纠察:它甚至能发现“死锁”这种复杂堵车,然后主动干预。
思考三:中央指挥中心如何管理“交通”?核心数据结构

Lock Manager 的“交通登记簿”并非简单的一本大账本,它内部是高度优化的数据结构。

核心武器:哈希表与链表

  1. 资源抽象 (LockResource):

    • 本质:“交通管制区域的精确标识”。
    • 在数据库里,我们要锁的“东西”很多:可能是一张products),可能是表里特定的一行数据id=10),还可能是两行数据之间的**“空隙”**(值为 510 之间的区域)。
    • Lock Manager 会把这些“东西”标准化,生成一个唯一的、可哈希的 LockResource 对象。它就像一个坐标系,能精准定位到被锁的区域。
    • 例子
      • {Type: TABLE_LOCK, TableId: product_table_ID}
      • {Type: RECORD_LOCK, TableId: product_table_ID, IndexId: primary_key_ID, RecordIdentifier: id_10}
      • {Type: GAP_LOCK, TableId: product_table_ID, IndexId: price_index_ID, GapBoundaries: (75, 300)}
  2. 锁对象 (LockObject):

    • 本质:“通行凭证卡片”。
    • 每个事务(TxID)发起一个锁请求时,Lock Manager 内部就会创建一个 LockObject
    • 这张卡片记录着:
      • 哪种通行模式? (锁模式):
        • X_LOCK (Exclusive Lock):独占通行,别人都不能过,包括看。
        • S_LOCK (Shared Lock):共享通行,大家都可以看,但谁都不能改。
        • IX_LOCK (Intention Exclusive Lock):我准备独占某个区域的小部分通行权。
        • IS_LOCK (Intention Shared Lock):我准备共享某个区域的小部分通行权。
        • GAP_LOCK (Gap Lock):我把一条路中间的空地拦起来,不让新车插队。
        • INSERT_INTENTION_LOCK:我想插队进去,如果空地被拦了我就等着。
      • 哪个司机持有? (事务ID:TxID)
      • 现在是不是在排队? (isWaiting: true/false)
      • 这张卡片是给哪个区域的? (TargetResource: LockResource)
  3. 全局锁哈希表 (globalLockHashTable):

    • 本质:“交通地图上的地标索引”。
    • 这是一个巨大的哈希表,它的键就是 LockResource(某个被锁定的区域标识)。
    • 它的值是一个链表,链表里面串着所有对这个 LockResource 发出的 LockObject。这个链表就像某个路口前排队的车辆,有的已经通行,有的还在等待。
    • 例子:当你查询 id=10 的锁状态时,Lock Manager 快速哈希到 Resource_Record_id_10 的位置,然后遍历其链表,就能知道谁锁了它,锁的模式是什么。
  4. 事务锁列表 (transactionLocksMap):

    • 本质:“每个司机的个人行驶记录本”。
    • 这是一个映射表,键是事务ID (TxID)
    • 值是该事务当前持有的所有 LockObject 的列表
    • 它的作用是,当一个事务提交或回滚时,Lock Manager 能迅速找到并释放它所持有的所有锁,而不需要扫描整个 globalLockHashTable

数据结构可视化:

否: 首次锁定
是: 资源已被锁定
是: 兼容
否: 不兼容
是: 自己持有冲突锁
能升级且无其他冲突
不能升级或有其他冲突
否: 其他事务持有冲突锁
起始: SQL语句请求锁
步骤1: 解析请求
生成LockResource
(例如: id=10的行)
步骤2: 查globalLockHashTable
是否有该资源的LockObject链表?
步骤3: 创建新的LockObject
Mode: X_LOCK, TxId: CurrentTx, isWaiting: false
步骤4: 将LockObject添加到
globalLockHashTable[LockResource]
并加入transactionLocksMap[TxId]
步骤5: 通知事务: 锁已获取
继续执行
步骤3: 遍历现有LockObject链表
检查兼容性 (LockMode.areCompatible?)
步骤4: 持有冲突锁的是否是
当前事务 (TxId) 自己?
步骤5: 尝试锁升级?
(例如S->X)
更新/替换LockObject模式
步骤6: 创建LockObject
isWaiting: true
步骤7: 加入等待队列
启动死锁检测
步骤8: 通知事务: 进入等待/被回滚
死锁检测
(构建等待图, 发现循环则回滚)
思考四:这些锁在各种 SQL 语句下如何站岗?

现在,我们把 Lock Manager 和它的“指挥”规则,与具体的 SQL 语句结合起来:

  1. INSERT (插入数据):

    • 表级:IX_LOCK。声明意图:我准备在表里加行了。
    • 间隙锁:INSERT_INTENTION_LOCK。我要往某个空隙里插车,得先拿到这块空地的“待插入”停车位票。如果这个空位被其他事务的 GAP_LOCK 挡住了,就得等待。
    • 行级:X_LOCK。新插入的行,当然得我独占,直到事务提交。
  2. UPDATE / DELETE (修改/删除数据):

    • 表级:IX_LOCK。同插入。
    • 行级:X_LOCK。无论修改还是删除,都得独占目标行。
    • 间隙/Next-Key Lock (尤其在 REPEATABLE READ 隔离级别,对范围查询):
      • 扫描到的每一条索引记录都会加 X_LOCK
      • 扫描过程中经过的所有索引间隙都会加 GAP_LOCK
      • Next-Key LockRECORD_LOCKGAP_LOCK 的组合,它锁定的是一个“区间” (前一个记录, 当前记录],用于彻底防止幻读和重复读。
  3. SELECT ... FOR UPDATE (查询并锁定,以备更新):

    • 表级:IX_LOCK
    • 行级:X_LOCK。对所有匹配到的行加 X_LOCK
    • 间隙/Next-Key Lock:行为与 UPDATE 类似,对扫描路径上的所有间隙加 GAP_LOCK。即使查询结果为空,但如果涉及到范围扫描,相关间隙仍可能被锁定。
  4. SELECT ... FOR SHARE (查询并共享锁定):

    • 表级:IS_LOCK。声明意图:我准备在表里看几行了。
    • 行级:S_LOCK。对所有匹配到的行加 S_LOCK。允许多个事务同时读取,但阻止任何事务加 X_LOCK 进行修改。
    • 间隙/Next-Key Lock:行为与 SELECT ... FOR UPDATE 类似,对扫描路径上的所有间隙加 GAP_LOCK
  5. 普通 SELECT (SELECT * FROM ...):

    • 在 InnoDB 的默认 REPEATABLE READ 隔离级别下,普通查询不加行锁。它依赖于 MVCC (多版本并发控制) 机制。
    • MVCC 提供的是一个事务开始时的快照视图,数据被读取的是历史版本,从而避免了读写冲突,提高了并发性。所以,Lock Manager 在这里不介入。

MySQL InnoDB 的锁机制,是一个精巧并发控制系统。

  • 它不是对数据的物理修改,而是权限的逻辑管理。
  • 它不是分散的个体行动,而是中心化的统一调度。
  • 它通过抽象资源、类型化锁、集中管理、智能判断,实现了在极致并发下对数据完整性和事务隔离性的滴水不漏的守护。

网站公告

今日签到

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