开发易忽视的问题:MySQL 死锁实现分析

377 阅读3分钟

分析MySQL的死锁问题需要深入理解InnoDB存储引擎的源码。InnoDB通过其内置的锁管理和死锁检测机制来处理并发事务中的死锁情况。

  1. 事务(Transaction)结构

    • 每个事务通常由一个trx_t结构表示,其中包含事务的状态、持有的锁列表、等待的锁请求等信息。
  2. 锁(Lock)结构

    • 锁可以是行锁或表锁,通常由lock_t结构表示。
    • 每个锁对象包含锁的类型(共享锁、独占锁),当前持有该锁的事务,以及可能等待该锁的其他事务。
  3. 等待队列(Wait Queue)

    • 每个锁都有一个与之关联的等待队列,用于管理多个事务对同一资源的争用。
    • 当一个事务请求的锁被其他事务持有时,它会被放入一个等待队列。
    • 等待队列用于跟踪所有未能立即获得锁的事务。
  4. 等待图(Wait-For Graph)

    • InnoDB构建一个等待图来跟踪哪些事务在等待哪些锁。
    • 图中每个节点代表一个事务,边表示一个事务正在等待另一个事务释放锁。
  5. 死锁检测

    • 通过遍历等待图,InnoDB检测循环依赖关系。
    • 检测到循环时,系统识别出参与死锁的事务,并选择一个事务作为牺牲者进行回滚,以打破死锁。
  6. 死锁检测算法

    • 使用深度优先搜索等算法遍历等待图,寻找环路来检测死锁。
  7. 选择牺牲者的策略

    • 事务执行时间:优先回滚运行时间较短的事务。这是因为长时间运行的事务可能已经进行了大量更新,回滚代价更高。

    • 修改的数据量:尽量选择修改数据量较少的事务进行回滚,以减少因撤销而带来的性能开销。

    • 事务系统版本号(Transaction ID) :通常选择系统版本号较新的事务。这对应于事务启动时间,ID较大的事务通常是“年轻”的事务。

    • 事务类型和重要性:某些情况下,可以根据业务逻辑为事务设置优先级,比如批处理事务可能被赋予比实时用户事务更低的优先级。

    • 锁等待时间:如果一个事务已经等待了很长时间,它可能会被优先保留下来,而不是作为牺牲者。

  8. 代码模块

    • 死锁检测和处理的核心代码主要集中在InnoDB的锁管理模块中,涉及诸如lock0lock.cc等源文件。
    • lock_deadlock_detect():这是一个关键函数,用于遍历等待图并寻找循环,从而检测死锁。
    • lock_rec_add_to_wait_queue():负责将事务加入等待队列,并触发死锁检测逻辑。
    • 函数如trx_rollback_for_mysql用于处理事务回滚。
  9. 回滚流程

    • 日志撤销:通过重做日志和撤销日志恢复数据到事务开始前的状态。
    • 锁释放:释放当前事务持有的所有锁,以解除对其他事务的阻塞。
    • 内存清理:清理与事务相关的内存资源,确保不会留下悬挂指针或资源泄漏。